الخوارزميات الموحدة المخصصة ، الجزء الأول: مقدمة إلى النواة الموحدة

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

هذا البرنامج التعليمي هو الجزء الأول من سلسلة الجزء الثاني الذي يوضح كيفية تنفيذ أنواع مخصصة من الخوارزميات الاتحادية في TensorFlow الاتحادية (TFF) باستخدام الاتحادية الأساسية (FC) - مجموعة من واجهات المستوى الأدنى أن تكون بمثابة الأساس الذي تقوم عليه لقد نفذت الاتحادية التعلم (FL) طبقة.

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

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

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

الاستخدامات المقصودة

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

تستطيع أن تكون على علم tf.contrib.distribute ، والسؤال الطبيعي أن نسأل في هذه المرحلة قد يكون: في ما طرق لا تختلف هذا الإطار؟ يحاول كلا الإطارين توزيع حسابات TensorFlow بعد كل شيء.

طريقة واحدة للتفكير في الامر هو أنه في حين أن الهدف المعلن من tf.contrib.distribute هو السماح للمستخدمين باستخدام النماذج الحالية ورمز التدريب مع تغيرات طفيفة لتمكين التدريب الموزعة، والكثير من التركيز على كيفية الاستفادة من البنية التحتية توزيعها لجعل كود التدريب الحالي أكثر كفاءة ، فإن الهدف من Federated Core التابع لـ TFF هو منح الباحثين والممارسين تحكمًا صريحًا في الأنماط المحددة للاتصال الموزع الذي سيستخدمونه في أنظمتهم. ينصب التركيز في FC على توفير لغة مرنة وقابلة للتوسيع للتعبير عن خوارزميات تدفق البيانات الموزعة ، بدلاً من مجموعة ملموسة من قدرات التدريب الموزعة المنفذة.

أحد الجماهير المستهدفة الأساسية لـ TFF's FC API هو الباحثون والممارسون الذين قد يرغبون في تجربة خوارزميات التعلم الموحدة الجديدة وتقييم عواقب خيارات التصميم الدقيقة التي تؤثر على الطريقة التي يتم بها تنظيم تدفق البيانات في النظام الموزع ، ومع ذلك دون التورط في تفاصيل تنفيذ النظام. يتوافق مستوى التجريد الذي تهدف إليه FC API تقريبًا مع الرمز الكاذب الذي يمكن للمرء استخدامه لوصف آليات خوارزمية التعلم الموحدة في منشور بحثي - ما هي البيانات الموجودة في النظام وكيف يتم تحويلها ، ولكن دون الانخفاض إلى مستوى تبادل رسائل شبكة فردية من نقطة إلى نقطة.

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

قبل أن نبدأ

قبل الغوص في الشفرة ، يرجى محاولة تشغيل مثال "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
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

البيانات الموحدة

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

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

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

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

federated_float_on_clients = tff.type_at_clients(tf.float32)

أكثر عموما، يتم تعريف نوع الاتحادية في TFF لتحديد نوع T المكونات الأعضاء فيها - بنود البيانات الموجودة على الأجهزة الفردية، ومجموعة G من الأجهزة التي يتم استضافتها القيم الاتحادية من هذا النوع (بالإضافة إلى الثلث، قطعة اختيارية من المعلومات سنذكرها بعد قليل). نشير إلى مجموعة G الأجهزة استضافة قيمة الاتحادية كما وضع قيمة ل. وهكذا، tff.CLIENTS هو مثال على التنسيب.

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

وهناك نوع الاتحادية مع الناخبين عضوا T والتنسيب G يمكن تمثيل مضغوط كما {T}@G ، كما هو مبين أدناه.

str(federated_float_on_clients)
'{float32}@CLIENTS'

الأقواس المعقوفة {} في هذا موجزة تدوين بمثابة تذكير بأن مكونات عضوا (البنود من البيانات على أجهزة مختلفة) قد تختلف، كما تتوقعون سبيل المثال، من القراءات استشعار درجة الحرارة، وبالتالي فإن عملاء كمجموعة تستضيف بالاشتراك مع متعددة -إعداد من T -typed العناصر التي تشكل في مجموعها قيمة الاتحادية.

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

تأتي الأنواع الموحدة في TFF في نسختين: تلك التي قد تختلف فيها مكونات العضو للقيمة الفيدرالية (كما رأينا أعلاه) ، وتلك التي من المعروف أنها متساوية. يتم التحكم بذلك عن طريق ثالث، اختياري all_equal معلمة في tff.FederatedType منشئ (التخلف إلى False ).

federated_float_on_clients.all_equal
False

وهناك نوع الاتحادية مع موضع G فيها كل من T -typed معروفة المكونات الأعضاء ليكون مساويا يمكن تمثيل مضغوط كما T@G (في مقابل {T}@G ، وهذا هو، مع الأقواس المعقوفة انخفض لتعكس حقيقة أن المجموعة المتعددة من الأعضاء المكونة تتكون من عنصر واحد).

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

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

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

على سبيل المثال، لنفترض أن لدينا زوج من float32 المعلمات a و b لنموذج الانحدار الخطي الأبعاد واحدة بسيطة. يمكننا بناء النوع (غير الفدرالي) من هذه النماذج لاستخدامها في TFF على النحو التالي. أقواس زاوية <> في سلسلة نوع المطبوعة هي تدوين TFF الصغير لالصفوف مسمى أو غير مسمى.

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

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

ملاحظة أننا تحديد فقط dtype الصورة أعلاه. الأنواع غير العددية مدعومة أيضًا. في رمز أعلاه، tf.float32 هو تدوين اختصار لأكثر عمومية tff.TensorType(dtype=tf.float32, shape=[]) .

عندما يتم بث هذا النموذج للعملاء ، يمكن تمثيل نوع القيمة الموحدة الناتجة كما هو موضح أدناه.

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

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

الآن، والعودة إلى القادمة float32@CLIENTS - في حين يبدو أنها تتكرر عبر وسائل متعددة، هو في الواقع واحد float32 ، لأن كل عضو هي نفسها. بشكل عام، قد تفكر في أي نوع الاتحادية للجميع على قدم المساواة، أي واحد من شكل T@G ، كما متماثل إلى غير الاتحادية نوع T ، لأنه في كلتا الحالتين، هناك في الواقع واحد فقط (وإن كان يحتمل تكرارها) البند من نوع T .

ونظرا للتماثل بين T و T@G ، قد نتساءل ما الغرض، إن وجدت، قد أنواع الأخيرة خدمتها. واصل القراءة.

المواضع

نظرة عامة على التصميم

في القسم السابق، أدخلنا مفهوم مواضع - مجموعات من المشاركين النظام التي قد تكون مشتركة استضافة قيمة الاتحادية، ولقد أثبت استخدام tff.CLIENTS كمثال مواصفات اكتتاب.

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

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

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

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

يمثل نوع من قيمة معينة كما T@G أو {T}@G (في مقابل عادل T ) تتخذ قرارات وضع بيانات صريحة، وجنبا إلى جنب مع تحليل ثابت من البرامج المكتوبة في TFF، فإنه يمكن أن تكون بمثابة الأساس لتوفير ضمانات الخصوصية الرسمية للبيانات الحساسة الموجودة على الجهاز.

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

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

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

تم تصميم مواضع ليكون مواطنا من الدرجة الأولى في TFF أيضا، ويمكن أن تظهر كمعلمات ونتائج placement نوع (في أن يمثله tff.PlacementType في API). في المستقبل ، نخطط لتوفير مجموعة متنوعة من المشغلين لتحويل المواضع أو دمجها ، ولكن هذا خارج نطاق هذا البرنامج التعليمي. في الوقت الراهن، ويكفي أن نفكر في placement باعتبارها مبهمة بدائية المدمج في نوع في TFF، مماثل لكيفية int و bool يتم بناؤها في مبهمة أنواع في بيثون، مع tff.CLIENTS كونه الحرفي ثابت من هذا النوع، وليس خلافا 1 يجري حرفي مستمر من نوع int .

تحديد المواضع

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

TFF لا يصف ما إما tff.CLIENTS أو tff.SERVER تمثل في الواقع.

على وجه الخصوص، tff.SERVER قد يكون جهاز فعلي واحد (عضو في مجموعة المفرد)، ولكن قد يكون فقط كذلك مجموعة من النسخ المتماثلة في تكرار آلة حالة الكتلة المتسامحة تشغيل - ونحن لا تجعل أي المعمارية الخاصة الافتراضات. وبدلا من ذلك، ونحن نستخدم all_equal الشيء المذكور في القسم السابق للتعبير عن حقيقة أننا نتعامل عادة مع عنصر واحد فقط من البيانات على الخادم.

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

الحسابات الموحدة

إعلان الحسابات الموحدة

تم تصميم TFF كبيئة برمجة وظيفية شديدة الكتابة تدعم التطوير المعياري.

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

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

وعند النظر إلى رمز أعلاه، في هذه المرحلة قد يكون السؤال - ليست هناك بالفعل الديكور يبني لتحديد وحدات composable مثل tf.function في TensorFlow، وإذا كان الأمر كذلك، لماذا إدخال احدة بعد أخرى، وكيف هي مختلفة؟

الجواب القصير هو أن الشفرة التي تم إنشاؤها من قبل tff.federated_computation المجمع ليست TensorFlow، ولا هو بيثون - انها مواصفات نظام توزيع في منصة مستقلة لغة الغراء الداخلية. في هذه المرحلة ، سيبدو هذا بلا شك غامضًا ، لكن من فضلك تحمل هذا التفسير البديهي للحساب الموحد كمواصفات مجردة لنظام موزع في الاعتبار. سنشرح ذلك في دقيقة.

أولاً ، دعنا نلعب مع التعريف قليلاً. يتم نمذجة حسابات TFF بشكل عام كوظائف - مع أو بدون معلمات ، ولكن مع تواقيع النوع المحددة جيدًا. يمكنك طباعة نوع التوقيع على حساب عن طريق الاستعلام في type_signature الملكية، كما هو مبين أدناه.

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

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

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

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

ومع ذلك، FC API لا تفرض هذا الافتراض، والعديد من اللبنات التي نستخدمها داخليا (بما في ذلك العديد من tff.federated_... المشغلين قد تجد في API) لديها المدخلات والمخرجات مع مواضع متميزة، وذلك في عام، يجب عليك لا نفكر في حساب الاتحادية كشيء الذي يعمل على الملقم أو يتم تنفيذها بواسطة الخادم. الخادم هو نوع واحد فقط من المشاركين في حساب موحد. عند التفكير في آليات مثل هذه الحسابات ، من الأفضل دائمًا التقصير في منظور الشبكة العالمية ، بدلاً من منظور منسق مركزي واحد.

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

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

تنفيذ الحسابات الموحدة

من أجل دعم التطوير والتصحيح ، يتيح لك TFF استدعاء الحسابات المحددة بهذه الطريقة على أنها وظائف Python ، كما هو موضح أدناه. حيث يتوقع حساب قيمة من نوع الاتحادية مع all_equal مجموعة بت لل False ، يمكنك إطعام بأنها مدنية list في بيثون، ولأنواع الاتحادية مع all_equal مجموعة قليلا إلى True ، يمكنك فقط إطعام مباشرة (واحد) عضو من أعضاء. هذه أيضًا هي الطريقة التي يتم بها إبلاغك بالنتائج.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

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

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

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

يمكنك التحقق من ذلك عن طريق إضافة بيان طباعة ، على النحو التالي:

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

يمكنك التفكير في كود Python الذي يحدد حسابًا متحدًا بشكل مشابه لكيفية تفكيرك في كود Python الذي ينشئ رسمًا بيانيًا TensorFlow في سياق غير متحمس (إذا لم تكن على دراية بالاستخدامات غير الشغوفة لـ TensorFlow ، ففكر في كود Python يحدد رسمًا بيانيًا للعمليات التي سيتم تنفيذها لاحقًا ، ولكن لا يتم تشغيلها في الواقع بسرعة). رمز بناء الرسم البياني غير المتحمّس في TensorFlow هو Python ، لكن الرسم البياني TensorFlow الذي تم إنشاؤه بواسطة هذا الرمز مستقل عن النظام الأساسي وقابل للتسلسل.

وبالمثل، يتم تعريف الحسابات TFF في بيثون، ولكن البيانات بيثون في أجسامهم، مثل tff.federated_mean في المثال [وف] أظهرت فقط، يتم تجميعها في التمثيل للتسلسل المحمولة ومنصة مستقلة تحت غطاء محرك السيارة.

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

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

سبب آخر هو أن حسابات TFF تعبر عن السلوك العالمي للأنظمة الموزعة ، على عكس برامج Python التي تعبر عن السلوك المحلي للمشاركين الفرديين. يمكنك أن ترى أن في مثال بسيط أعلاه، مع مشغل خاص tff.federated_mean أن يقبل البيانات على أجهزة العميل، ولكن الودائع النتائج على الخادم.

المشغل tff.federated_mean لا يمكن أن تكون على غرار بسهولة كمشغل العاديين في بيثون، لأنه لا يتم تنفيذ محليا - كما ذكر في وقت سابق، لأنها تمثل نظام توزيع تلك الإحداثيات سلوك المشاركين في النظام المتعدد. وسوف نشير إلى هذه مشغلي كما مشغلي الاتحادية، لتمييزها عن العادية مشغلي (المحلية) في بيثون.

نظام نوع TFF ، ومجموعة العمليات الأساسية المدعومة بلغة TFF ، تنحرف بشكل كبير عن تلك الموجودة في Python ، مما يستلزم استخدام تمثيل مخصص.

يؤلف الحسابات الموحدة

كما هو مذكور أعلاه ، من الأفضل فهم الحسابات الموحدة ومكوناتها كنماذج للأنظمة الموزعة ، ويمكنك التفكير في تكوين حسابات موحدة على أنها تتألف من أنظمة موزعة أكثر تعقيدًا من أنظمة أبسط. يمكنك التفكير في tff.federated_mean مشغل كنوع من المدمج في قالب حساب الاتحادية مع توقيع نوع ({T}@CLIENTS -> T@SERVER) (في الواقع، تماما مثل حسابات تكتب، وهذا المشغل لديه أيضا مجمع الهيكل - تحت الغطاء نقسمه إلى مشغلين أبسط).

وينطبق الشيء نفسه على تأليف الحسابات الفيدرالية. حساب get_average_temperature يجوز التذرع في الجسم وظيفة بيثون آخر مزينة tff.federated_computation - وبذلك يؤدي إلى تكون جزءا لا يتجزأ في جسم الأم، والكثير في بنفس الطريقة tff.federated_mean كان جزءا لا يتجزأ من الجسم الخاصة في وقت سابق.

تقييد المهم أن يكون على بينة من أن الهيئات وظائف بيثون مزينة tff.federated_computation يجب أن تتكون فقط من مشغلي الاتحادية، أي أنها لا يمكن أن تحتوي على مباشرة عمليات TensorFlow. على سبيل المثال، لا يمكنك استخدام مباشرة tf.nest واجهات لإضافة زوج من قيم الاتحادية. يجب أن يقتصر كود TensorFlow إلى كتل من التعليمات البرمجية مزينة tff.tf_computation مناقشتها في القسم التالي. فقط عندما ملفوفة بهذه الطريقة يمكن التذرع رمز TensorFlow ملفوفة في الجسم من tff.federated_computation .

أسباب هذا الانفصال هي التقنية (انه من الصعب خداع شركات مثل tf.add للعمل مع غير التنسورات) وكذلك المعمارية. لغة الحسابات الاتحادية (أي منطق شيدت من الهيئات تسلسل وظائف بيثون مزينة tff.federated_computation ) تم تصميمه ليكون بمثابة لغة الغراء منصة مستقلة. وتستخدم هذه اللغة الغراء حاليا لأنظمة البناء وزعت من أقسام جزءا لا يتجزأ من التعليمات البرمجية TensorFlow (تقتصر على tff.tf_computation بنات). في ملء الزمان، فإننا نتوقع الحاجة إلى أقسام تضمين من الآخرين المنطق، غير TensorFlow، مثل استعلامات قاعدة البيانات العلائقية التي قد تمثل خطوط الأنابيب المدخلات، وكلها متصلة معا باستخدام نفس اللغة الغراء (و tff.federated_computation بنات).

منطق TensorFlow

إعلان حسابات TensorFlow

تم تصميم TFF للاستخدام مع TensorFlow. على هذا النحو ، فإن الجزء الأكبر من الكود الذي ستكتبه في TFF من المرجح أن يكون كود TensorFlow عادي (أي يتم تنفيذه محليًا). من أجل استخدام هذا الرمز مع TFF، كما ذكر أعلاه، فإنه يحتاج فقط إلى أن تكون مزينة tff.tf_computation .

على سبيل المثال، وهنا كيف يمكننا تنفيذ دالة التي تأخذ رقما ويضيف 0.5 إليها.

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

مرة أخرى، وتبحث في هذا، هل يمكن أن يتساءل لماذا يجب علينا تحديد الديكور آخر tff.tf_computation بدلا من مجرد استخدام الآلية القائمة مثل tf.function . على عكس القسم السابق ، نحن هنا نتعامل مع كتلة عادية من كود TensorFlow.

هناك عدة أسباب لذلك ، تتجاوز معالجتها الكاملة نطاق هذا البرنامج التعليمي ، لكن الأمر يستحق تسمية السبب الرئيسي:

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

بشكل عام، ونحن نوصي باستخدام آليات الأم TensorFlow لتكوين مثل tf.function ، حيثما كان ذلك ممكنا، والطريقة الدقيقة التي يتفاعل الديكور TFF مع وظائف حريصة من المتوقع أن تتطور.

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

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

لاحظ أن هذا النوع من التوقيع لا يحتوي على مواضع. لا يمكن أن تستهلك حسابات TensorFlow أو تعيد الأنواع الموحدة.

يمكنك الآن أيضا استخدام add_half بوصفها لبنة في حسابات أخرى. على سبيل المثال، وهنا كيف يمكنك استخدام tff.federated_map مشغل لتطبيق add_half pointwise لجميع مكونات عضو في تعويم الاتحادية على أجهزة العميل.

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

تنفيذ حسابات TensorFlow

تنفيذ العمليات الحسابية المعرفة مع tff.tf_computation يتبع نفس القواعد التي وصفنا لل tff.federated_computation . يمكن استدعاؤها على أنها عناصر استدعاء عادية في بايثون ، على النحو التالي.

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

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

أيضا، أن نضع في الاعتبار أن ما قلناه في القسم السابق عن حسابات TFF الحصول على تسلسل في الوقت تعريف يبقى صحيحا ل tff.tf_computation كود كذلك - الجسم بيثون من add_half_on_clients يحصل تتبع مرة واحدة في وقت والوضوح. في الاستدعاءات اللاحقة ، يستخدم TFF تمثيله المتسلسل.

والفرق الوحيد بين وسائل بيثون مزينة tff.federated_computation وتلك مزينة tff.tf_computation هو أن تسلسل هذه الأخيرة كما الرسوم البيانية TensorFlow (في حين لا يسمح السابق لتحتوي على رمز TensorFlow جزءا لا يتجزأ مباشرة في نفوسهم).

تحت غطاء محرك السيارة، ولكل طريقة مزينة tff.tf_computation تعطيل مؤقتا تنفيذ حريصة من أجل السماح للبنية حساب ليتم القبض. بينما يتم تعطيل التنفيذ المتوق محليًا ، فنحن نرحب بك لاستخدام تركيبات TensorFlow و AutoGraph و TensorFlow 2.0 الحريصة وما إلى ذلك ، طالما أنك تكتب منطق الحساب الخاص بك بطريقة تجعله متسلسلًا بشكل صحيح.

على سبيل المثال ، ستفشل الكود التالي:

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.

وفشل أعلاه لأن constant_10 قد تم بناؤها خارج الرسم البياني الذي tff.tf_computation يبني داخليا في جسم add_ten أثناء عملية التسلسل.

من ناحية أخرى، استدعاء وظائف الثعبان أن تعديل الرسم البياني الحالي عندما دعا داخل tff.tf_computation على ما يرام:

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

لاحظ أن آليات التسلسل في TensorFlow تتطور ، ونتوقع أن تتطور تفاصيل كيفية تسلسل TFF للحسابات أيضًا.

العمل مع tf.data.Dataset الصورة

وكما ذكرنا سابقا، وهي ميزة فريدة من tff.tf_computation الصورة هي أنها تسمح لك للعمل مع tf.data.Dataset الصورة المحددة تجريدي كمعلمات رسمية من التعليمات البرمجية. المعلمات في أن تكون ممثلة في TensorFlow كما لا بد من تعريف باستخدام مجموعات البيانات tff.SequenceType المنشئ.

على سبيل المثال، نوع مواصفات tff.SequenceType(tf.float32) يحدد تسلسل مجردة من العناصر تطفو في TFF. يمكن أن تحتوي التسلسلات إما على الموترات أو الهياكل المعقدة المتداخلة (سنرى أمثلة على ذلك لاحقًا). تمثيل موجزة من سلسلة من T -typed المواد و 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 .