إعادة الإعمار الموحدة لعامل المصفوفة

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

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

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

!pip install --quiet --upgrade tensorflow-federated-nightly
!pip install --quiet --upgrade nest-asyncio

import nest_asyncio
nest_asyncio.apply()
import collections
import functools
import io
import os
import requests
import zipfile
from typing import List, Optional, Tuple

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_federated as tff

np.random.seed(42)

الخلفية: عامل المصفوفة

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

لهذه المهمة، فإنه من المفيد أن تمثل بتصنيفات المستخدمين باعتبارها \(n \times m\) مصفوفة \(R\):

دافع عامل المصفوفة (CC BY-SA 3.0 ؛ مستخدم ويكيبيديا موشينين)

هذه المصفوفة متفرقة بشكل عام ، لأن المستخدمين عادة لا يرون سوى جزء صغير من الأفلام في مجموعة البيانات. خرج الى عوامل مصفوفة اثنين من المصفوفات: و \(n \times k\) مصفوفة \(U\) يمثلون \(k\)التضمينات المستخدم الأبعاد لكل مستخدم، و \(m \times k\) مصفوفة \(I\) تمثل \(k\)التضمينات البند الابعاد لكل عنصر. الهدف تدريب أبسط هو التأكد من أن المنتج نقطة من المستخدمين وبند التضمينات والتنبؤي التقييم المرصودة \(O\):

\[argmin_{U,I} \sum_{(u, i) \in O} (R_{ui} - U_u I_i^T)^2\]

هذا يعادل تقليل متوسط ​​الخطأ التربيعي بين التصنيفات المرصودة والتصنيفات المتوقعة من خلال أخذ المنتج النقطي للمستخدم المقابل وحفلات الزفاف. وهناك طريقة أخرى لتفسير ذلك هو أن هذا يضمن أن \(R \approx UI^T\) للتصنيفات معروفة، وبالتالي "الى عوامل المصفوفة". إذا كان هذا محيرًا ، فلا تقلق - فلن نحتاج إلى معرفة تفاصيل عامل المصفوفة لبقية البرنامج التعليمي.

استكشاف بيانات MovieLens

دعونا نبدأ عن طريق تحميل MovieLens 1M البيانات، والذي يتكون من 1000209 تصنيفات فيلم من 6040 المستخدمين على 3706 الأفلام.

def download_movielens_data(dataset_path):
  """Downloads and copies MovieLens data to local /tmp directory."""
  if dataset_path.startswith('http'):
    r = requests.get(dataset_path)
    z = zipfile.ZipFile(io.BytesIO(r.content))
    z.extractall(path='/tmp')
  else:
    tf.io.gfile.makedirs('/tmp/ml-1m/')
    for filename in ['ratings.dat', 'movies.dat', 'users.dat']:
      tf.io.gfile.copy(
          os.path.join(dataset_path, filename),
          os.path.join('/tmp/ml-1m/', filename),
          overwrite=True)

download_movielens_data('http://files.grouplens.org/datasets/movielens/ml-1m.zip')
def load_movielens_data(
    data_directory: str = "/tmp",
) -> Tuple[pd.DataFrame, pd.DataFrame]:
  """Loads pandas DataFrames for ratings, movies, users from data directory."""
  # Load pandas DataFrames from data directory. Assuming data is formatted as
  # specified in http://files.grouplens.org/datasets/movielens/ml-1m-README.txt.
  ratings_df = pd.read_csv(
      os.path.join(data_directory, "ml-1m", "ratings.dat"),
      sep="::",
      names=["UserID", "MovieID", "Rating", "Timestamp"], engine="python")
  movies_df = pd.read_csv(
      os.path.join(data_directory, "ml-1m", "movies.dat"),
      sep="::",
      names=["MovieID", "Title", "Genres"], engine="python")

  # Create dictionaries mapping from old IDs to new (remapped) IDs for both
  # MovieID and UserID. Use the movies and users present in ratings_df to
  # determine the mapping, since movies and users without ratings are unneeded.
  movie_mapping = {
      old_movie: new_movie for new_movie, old_movie in enumerate(
          ratings_df.MovieID.astype("category").cat.categories)
  }
  user_mapping = {
      old_user: new_user for new_user, old_user in enumerate(
          ratings_df.UserID.astype("category").cat.categories)
  }

  # Map each DataFrame consistently using the now-fixed mapping.
  ratings_df.MovieID = ratings_df.MovieID.map(movie_mapping)
  ratings_df.UserID = ratings_df.UserID.map(user_mapping)
  movies_df.MovieID = movies_df.MovieID.map(movie_mapping)

  # Remove nulls resulting from some movies being in movies_df but not
  # ratings_df.
  movies_df = movies_df[pd.notnull(movies_df.MovieID)]

  return ratings_df, movies_df

دعنا نحمل ونستكشف زوجًا من إطارات بيانات Pandas التي تحتوي على بيانات التصنيف والأفلام.

ratings_df, movies_df = load_movielens_data()

يمكننا أن نرى أن كل مثال تصنيف له تصنيف من 1-5 ، ومعرف مستخدم مطابق ، ومعرف MovieID مطابق ، وطابع زمني.

ratings_df.head()

كل فيلم له عنوان وأنواع متعددة.

movies_df.head()

من الجيد دائمًا فهم الإحصائيات الأساسية لمجموعة البيانات:

print('Num users:', len(set(ratings_df.UserID)))
print('Num movies:', len(set(ratings_df.MovieID)))
Num users: 6040
Num movies: 3706
ratings = ratings_df.Rating.tolist()

plt.hist(ratings, bins=5)
plt.xticks([1, 2, 3, 4, 5])
plt.ylabel('Count')
plt.xlabel('Rating')
plt.show()

print('Average rating:', np.mean(ratings))
print('Median rating:', np.median(ratings))

بي إن جي

Average rating: 3.581564453029317
Median rating: 4.0

يمكننا أيضًا رسم أنواع الأفلام الأكثر شيوعًا.

movie_genres_list = movies_df.Genres.tolist()
# Count the number of times each genre describes a movie.
genre_count = collections.defaultdict(int)
for genres in movie_genres_list:
  curr_genres_list = genres.split('|')
  for genre in curr_genres_list:
    genre_count[genre] += 1
genre_name_list, genre_count_list = zip(*genre_count.items())

plt.figure(figsize=(11, 11))
plt.pie(genre_count_list, labels=genre_name_list)
plt.title('MovieLens Movie Genres')
plt.show()

بي إن جي

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

def print_top_genres_for_user(ratings_df, movies_df, user_id):
  """Prints top movie genres for user with ID user_id."""
  user_ratings_df = ratings_df[ratings_df.UserID == user_id]
  movie_ids = user_ratings_df.MovieID

  genre_count = collections.Counter()
  for movie_id in movie_ids:
    genres_string = movies_df[movies_df.MovieID == movie_id].Genres.tolist()[0]
    for genre in genres_string.split('|'):
      genre_count[genre] += 1

  print(f'\nFor user {user_id}:')
  for (genre, freq) in genre_count.most_common(5):
    print(f'{genre} was rated {freq} times')

print_top_genres_for_user(ratings_df, movies_df, user_id=0)
print_top_genres_for_user(ratings_df, movies_df, user_id=10)
print_top_genres_for_user(ratings_df, movies_df, user_id=19)
For user 0:
Drama was rated 21 times
Children's was rated 20 times
Animation was rated 18 times
Musical was rated 14 times
Comedy was rated 14 times

For user 10:
Comedy was rated 84 times
Drama was rated 54 times
Romance was rated 22 times
Thriller was rated 18 times
Action was rated 9 times

For user 19:
Action was rated 17 times
Sci-Fi was rated 9 times
Thriller was rated 9 times
Drama was rated 6 times
Crime was rated 5 times

المعالجة المسبقة لبيانات MovieLens

سنقوم الآن إعداد ورقة العمل MovieLens كقائمة tf.data.Dataset الصورة تمثل بيانات كل مستخدم للاستخدام مع TFF.

نقوم بتنفيذ وظيفتين:

  • create_tf_datasets : يأخذ لدينا تصنيفات DataFrame وتنتج قائمة من سهولة انقسام tf.data.Dataset الصورة.
  • split_tf_datasets : يأخذ على قائمة من مجموعات البيانات وانقسامات لهم في قطار / فال / اختبار من قبل المستخدم، وبالتالي فإن فال / مجموعات اختبار تحتوي على تصنيفات فقط من المستخدمين غير مرئي أثناء التدريب. عادة في القياسية إلى عوامل مصفوفة مركزية يمكننا تقسيم الواقع بحيث مجموعات فال / اختبار تحتوي المحمولة من التصنيفات من المستخدمين رأيت، منذ المستخدمين الغيب لا تملك التضمينات المستخدم. في حالتنا ، سنرى لاحقًا أن النهج الذي نستخدمه لتمكين عامل المصفوفة في FL يتيح أيضًا إعادة بناء زخارف المستخدم للمستخدمين غير المرئيين.
def create_tf_datasets(ratings_df: pd.DataFrame,
                       batch_size: int = 1,
                       max_examples_per_user: Optional[int] = None,
                       max_clients: Optional[int] = None) -> List[tf.data.Dataset]:
  """Creates TF Datasets containing the movies and ratings for all users."""
  num_users = len(set(ratings_df.UserID))
  # Optionally limit to `max_clients` to speed up data loading.
  if max_clients is not None:
    num_users = min(num_users, max_clients)

  def rating_batch_map_fn(rating_batch):
    """Maps a rating batch to an OrderedDict with tensor values."""
    # Each example looks like: {x: movie_id, y: rating}.
    # We won't need the UserID since each client will only look at their own
    # data.
    return collections.OrderedDict([
        ("x", tf.cast(rating_batch[:, 1:2], tf.int64)),
        ("y", tf.cast(rating_batch[:, 2:3], tf.float32))
    ])

  tf_datasets = []
  for user_id in range(num_users):
    # Get subset of ratings_df belonging to a particular user.
    user_ratings_df = ratings_df[ratings_df.UserID == user_id]

    tf_dataset = tf.data.Dataset.from_tensor_slices(user_ratings_df)

    # Define preprocessing operations.
    tf_dataset = tf_dataset.take(max_examples_per_user).shuffle(
        buffer_size=max_examples_per_user, seed=42).batch(batch_size).map(
        rating_batch_map_fn,
        num_parallel_calls=tf.data.experimental.AUTOTUNE)
    tf_datasets.append(tf_dataset)

  return tf_datasets


def split_tf_datasets(
    tf_datasets: List[tf.data.Dataset],
    train_fraction: float = 0.8,
    val_fraction: float = 0.1,
) -> Tuple[List[tf.data.Dataset], List[tf.data.Dataset], List[tf.data.Dataset]]:
  """Splits a list of user TF datasets into train/val/test by user.
  """
  np.random.seed(42)
  np.random.shuffle(tf_datasets)

  train_idx = int(len(tf_datasets) * train_fraction)
  val_idx = int(len(tf_datasets) * (train_fraction + val_fraction))

  # Note that the val and test data contains completely different users, not
  # just unseen ratings from train users.
  return (tf_datasets[:train_idx], tf_datasets[train_idx:val_idx],
          tf_datasets[val_idx:])
# We limit the number of clients to speed up dataset creation. Feel free to pass
# max_clients=None to load all clients' data.
tf_datasets = create_tf_datasets(
    ratings_df=ratings_df,
    batch_size=5,
    max_examples_per_user=300,
    max_clients=2000)

# Split the ratings into training/val/test by client.
tf_train_datasets, tf_val_datasets, tf_test_datasets = split_tf_datasets(
    tf_datasets,
    train_fraction=0.8,
    val_fraction=0.1)

كتحقق سريع ، يمكننا طباعة مجموعة من بيانات التدريب. يمكننا أن نرى أن كل مثال على حدة يحتوي على MovieID تحت مفتاح "x" وتصنيف تحت مفتاح "y". لاحظ أننا لن نحتاج إلى معرف المستخدم لأن كل مستخدم يرى بياناته الخاصة فقط.

print(next(iter(tf_train_datasets[0])))
OrderedDict([('x', <tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[1907],
       [2891],
       [1574],
       [2785],
       [2775]])>), ('y', <tf.Tensor: shape=(5, 1), dtype=float32, numpy=
array([[3.],
       [3.],
       [3.],
       [4.],
       [3.]], dtype=float32)>)])

يمكننا رسم رسم بياني يوضح عدد التقييمات لكل مستخدم.

def count_examples(curr_count, batch):
  return curr_count + tf.size(batch['x'])

num_examples_list = []
# Compute number of examples for every other user.
for i in range(0, len(tf_train_datasets), 2):
  num_examples = tf_train_datasets[i].reduce(tf.constant(0), count_examples).numpy()
  num_examples_list.append(num_examples)

plt.hist(num_examples_list, bins=10)
plt.ylabel('Count')
plt.xlabel('Number of Examples')
plt.show()

بي إن جي

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

إحضار عامل المصفوفة إلى FL

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

اقتراح واحد لجلب عامل المصفوفة إلى FL يذهب على النحو التالي:

  1. مخازن الخادم ويرسل هذا البند مصفوفة \(I\) للعملاء عينات كل جولة
  2. عملاء بتحديث هذا البند المصفوفة والمستخدم الشخصية التضمين \(U_u\) باستخدام SGD على الهدف المذكور أعلاه
  3. تحديثات \(I\) يتم تجميع على الخادم، وتحديث نسخة الملقم من \(I\) للجولة القادمة

هذا النهج هو محلي جزئيا، بمعنى أنه يتم أبدا تجميع بعض المعلمات العميل من قبل الملقم. على الرغم من أن هذا النهج جذاب ، إلا أنه يتطلب من العملاء الحفاظ على الحالة عبر الجولات ، أي حفلات الزفاف الخاصة بهم. تعد الخوارزميات الموحدة ذات الحالة أقل ملاءمة لإعدادات FL عبر الأجهزة: في هذه الإعدادات ، غالبًا ما يكون حجم السكان أكبر بكثير من عدد العملاء الذين يشاركون في كل جولة ، وعادة ما يشارك العميل مرة واحدة على الأكثر أثناء عملية التدريب. وبالاضافة الى الاعتماد على الدولة التي قد لا يتم تهيئتها، يمكن الخوارزميات جليل يؤدي إلى تدهور الأداء في إعدادات عبر الجهاز بسبب حالة الحصول قديمة عندما يتم أخذ عينات للعملاء بشكل غير منتظم. الأهم من ذلك ، في إعداد عامل المصفوفة ، تؤدي الخوارزمية ذات الحالة إلى فقدان جميع العملاء غير المرئيين لزفاف المستخدم المدربين ، وفي التدريب على نطاق واسع ، قد يكون غالبية المستخدمين غير مرئيين. لمعرفة المزيد عن الدافع لخوارزميات البدون في عبر جهاز FL، انظر وانغ وآخرون. 2021 ثانية. 3.1.1 و Reddi وآخرون. 2020 ثانية. 5.1 .

اتحاد التعمير ( سينغال وآخرون 2021 ) هو بديل عديمي الجنسية إلى النهج المذكور. الفكرة الأساسية هي أنه بدلاً من تخزين زينة المستخدم عبر الجولات ، يقوم العملاء بإعادة بناء حفلات الزفاف عند الحاجة. عند تطبيق FedRecon على عامل المصفوفة ، يستمر التدريب على النحو التالي:

  1. مخازن الخادم ويرسل هذا البند مصفوفة \(I\) للعملاء عينات كل جولة
  2. يتجمد كل عميل \(I\) ويدرب على المستخدم تضمين \(U_u\) باستخدام واحد أو أكثر من الخطوات SGD (إعادة البناء)
  3. يتجمد كل عميل \(U_u\) والقطارات \(I\) باستخدام واحد أو أكثر من الخطوات SGD
  4. تحديثات \(I\) يتم تجميع عبر المستخدمين وتحديث نسخة الملقم من \(I\) للجولة القادمة

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

تحديد النموذج

سنقوم بعد ذلك بتحديد نموذج عامل المصفوفة المحلي ليتم تدريبه على أجهزة العميل. وسيشمل هذا النموذج الكامل البند مصفوفة \(I\) وتضمين مستخدم واحد \(U_u\) للعميل \(u\). لاحظ أن العملاء لن تحتاج لتخزين كامل المصفوفة المستخدم \(U\).

سنحدد ما يلي:

  • UserEmbedding : طبقة Keras بسيطة تمثل واحدة num_latent_factors تضمين المستخدم الأبعاد.
  • get_matrix_factorization_model : وظيفة أن عائدات tff.learning.reconstruction.Model تحتوي على منطق النموذج، بما في ذلك الطبقات التي يتم تجميع عالميا على الخادم والتي طبقات تبقى المحلي. نحتاج إلى هذه المعلومات الإضافية لبدء عملية التدريب على إعادة الإعمار الموحدة. نحن هنا تنتج tff.learning.reconstruction.Model من نموذج Keras باستخدام tff.learning.reconstruction.from_keras_model . على غرار tff.learning.Model ، يمكننا أيضا تنفيذ مخصص tff.learning.reconstruction.Model من خلال تنفيذ واجهة الصف.
class UserEmbedding(tf.keras.layers.Layer):
  """Keras layer representing an embedding for a single user, used below."""

  def __init__(self, num_latent_factors, **kwargs):
    super().__init__(**kwargs)
    self.num_latent_factors = num_latent_factors

  def build(self, input_shape):
    self.embedding = self.add_weight(
        shape=(1, self.num_latent_factors),
        initializer='uniform',
        dtype=tf.float32,
        name='UserEmbeddingKernel')
    super().build(input_shape)

  def call(self, inputs):
    return self.embedding

  def compute_output_shape(self):
    return (1, self.num_latent_factors)


def get_matrix_factorization_model(
    num_items: int,
    num_latent_factors: int) -> tff.learning.reconstruction.Model:
  """Defines a Keras matrix factorization model."""
  # Layers with variables will be partitioned into global and local layers.
  # We'll pass this to `tff.learning.reconstruction.from_keras_model`.
  global_layers = []
  local_layers = []

  # Extract the item embedding.
  item_input = tf.keras.layers.Input(shape=[1], name='Item')
  item_embedding_layer = tf.keras.layers.Embedding(
      num_items,
      num_latent_factors,
      name='ItemEmbedding')
  global_layers.append(item_embedding_layer)
  flat_item_vec = tf.keras.layers.Flatten(name='FlattenItems')(
      item_embedding_layer(item_input))

  # Extract the user embedding.
  user_embedding_layer = UserEmbedding(
      num_latent_factors,
      name='UserEmbedding')
  local_layers.append(user_embedding_layer)

  # The item_input never gets used by the user embedding layer,
  # but this allows the model to directly use the user embedding.
  flat_user_vec = user_embedding_layer(item_input)

  # Compute the dot product between the user embedding, and the item one.
  pred = tf.keras.layers.Dot(
      1, normalize=False, name='Dot')([flat_user_vec, flat_item_vec])

  input_spec = collections.OrderedDict(
      x=tf.TensorSpec(shape=[None, 1], dtype=tf.int64),
      y=tf.TensorSpec(shape=[None, 1], dtype=tf.float32))

  model = tf.keras.Model(inputs=item_input, outputs=pred)

  return tff.learning.reconstruction.from_keras_model(
      keras_model=model,
      global_layers=global_layers,
      local_layers=local_layers,
      input_spec=input_spec)

Analagous إلى واجهة للاتحاد المتوسط، واجهة للاتحاد تعمير تتوقع model_fn بدون وسائط أن عائدات tff.learning.reconstruction.Model .

# This will be used to produce our training process.
# User and item embeddings will be 50-dimensional.
model_fn = functools.partial(
    get_matrix_factorization_model,
    num_items=3706,
    num_latent_factors=50)

ونحن سوف تحدد المقبل loss_fn و metrics_fn ، حيث loss_fn هي وظيفة لا حجة عودته خسارة Keras لاستخدامها لتدريب نموذج، و metrics_fn هي وظيفة لا حجة العودة قائمة مقاييس Keras للتقييم. هذه مطلوبة لبناء حسابات التدريب والتقييم.

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

class RatingAccuracy(tf.keras.metrics.Mean):
  """Keras metric computing accuracy of reconstructed ratings."""

  def __init__(self,
               name: str = 'rating_accuracy',
               **kwargs):
    super().__init__(name=name, **kwargs)

  def update_state(self,
                   y_true: tf.Tensor,
                   y_pred: tf.Tensor,
                   sample_weight: Optional[tf.Tensor] = None):
    absolute_diffs = tf.abs(y_true - y_pred)
    # A [batch_size, 1] tf.bool tensor indicating correctness within the
    # threshold for each example in a batch. A 0.5 threshold corresponds
    # to correctness when predictions are rounded to the nearest whole
    # number.
    example_accuracies = tf.less_equal(absolute_diffs, 0.5)
    super().update_state(example_accuracies, sample_weight=sample_weight)


loss_fn = lambda: tf.keras.losses.MeanSquaredError()
metrics_fn = lambda: [RatingAccuracy()]

التدريب والتقييم

الآن لدينا كل ما نحتاجه لتحديد عملية التدريب. فارق واحد مهم من واجهة للاتحاد المتوسط هو أن نعبر الآن في reconstruction_optimizer_fn ، والتي سيتم استخدامها عند اعادة المعلمات المحلية (في حالتنا، التضمينات المستخدم). فمن المعقول عموما لاستخدام SGD هنا، مع مماثلة أو أقل قليلا معدل التعلم من العميل محسن معدل التعلم. نحن نقدم تكوين العمل أدناه. لم يتم ضبط هذا بعناية ، لذا لا تتردد في اللعب بقيم مختلفة.

تحقق من الوثائق لمزيد من التفاصيل والخيارات.

# We'll use this by doing:
# state = training_process.initialize()
# state, metrics = training_process.next(state, federated_train_data)
training_process = tff.learning.reconstruction.build_training_process(
    model_fn=model_fn,
    loss_fn=loss_fn,
    metrics_fn=metrics_fn,
    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(1.0),
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(0.5),
    reconstruction_optimizer_fn=lambda: tf.keras.optimizers.SGD(0.1))

يمكننا أيضًا تحديد طريقة حسابية لتقييم نموذجنا العالمي المُدرَّب.

# We'll use this by doing:
# eval_metrics = evaluation_computation(state.model, tf_val_datasets)
# where `state` is the state from the training process above.
evaluation_computation = tff.learning.reconstruction.build_federated_evaluation(
    model_fn,
    loss_fn=loss_fn,
    metrics_fn=metrics_fn,
    reconstruction_optimizer_fn=functools.partial(
            tf.keras.optimizers.SGD, 0.1))

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

state = training_process.initialize()
print(state.model)
print('Item variables shape:', state.model.trainable[0].shape)
ModelWeights(trainable=[array([[-0.02840446,  0.01196523, -0.01864688, ...,  0.03020107,
         0.00121176,  0.00146852],
       [ 0.01330637,  0.04741272, -0.01487445, ..., -0.03352419,
         0.0104811 ,  0.03506917],
       [-0.04132779,  0.04883525, -0.04799002, ...,  0.00246904,
         0.00586842,  0.01506213],
       ...,
       [ 0.0216659 ,  0.00734354,  0.00471039, ...,  0.01596491,
        -0.00220431, -0.01559857],
       [-0.00319657, -0.01740328,  0.02808609, ..., -0.00501985,
        -0.03850871, -0.03844522],
       [ 0.03791947, -0.00035037,  0.04217024, ...,  0.00365371,
         0.00283421,  0.00897921]], dtype=float32)], non_trainable=[])
Item variables shape: (3706, 50)

يمكننا أيضًا محاولة تقييم نموذجنا الذي تمت تهيئته عشوائيًا على عملاء التحقق. يتضمن تقييم إعادة الإعمار الموحد هنا ما يلي:

  1. خادم يرسل البند مصفوفة \(I\) للعملاء تقييم عينات
  2. يتجمد كل عميل \(I\) ويدرب على المستخدم تضمين \(U_u\) باستخدام واحد أو أكثر من الخطوات SGD (إعادة البناء)
  3. كل خسارة بحساب العميل والمقاييس باستخدام ملقم \(I\) وأعيد بناؤها \(U_u\) على جزء غير مرئي من البيانات المحلية
  4. يتم حساب متوسط ​​الخسائر والمقاييس عبر المستخدمين لحساب الخسارة الإجمالية والمقاييس

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

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

# We shouldn't expect good evaluation results here, since we haven't trained
# yet!
eval_metrics = evaluation_computation(state.model, tf_val_datasets)
print('Initial Eval:', eval_metrics['eval'])
Initial Eval: OrderedDict([('loss', 14.340279), ('rating_accuracy', 0.0)])

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

federated_train_data = np.random.choice(tf_train_datasets, size=50, replace=False).tolist()
state, metrics = training_process.next(state, federated_train_data)
print(f'Train metrics:', metrics['train'])
Train metrics: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.317455)])

لنقم الآن بإعداد حلقة تدريب للتدريب على جولات متعددة.

NUM_ROUNDS = 20

train_losses = []
train_accs = []

state = training_process.initialize()

# This may take a couple minutes to run.
for i in range(NUM_ROUNDS):
  federated_train_data = np.random.choice(tf_train_datasets, size=50, replace=False).tolist()
  state, metrics = training_process.next(state, federated_train_data)
  print(f'Train round {i}:', metrics['train'])
  train_losses.append(metrics['train']['loss'])
  train_accs.append(metrics['train']['rating_accuracy'])


eval_metrics = evaluation_computation(state.model, tf_val_datasets)
print('Final Eval:', eval_metrics['eval'])
Train round 0: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.7013445)])
Train round 1: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.459233)])
Train round 2: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.52466)])
Train round 3: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.087793)])
Train round 4: OrderedDict([('rating_accuracy', 0.011243612), ('loss', 11.110232)])
Train round 5: OrderedDict([('rating_accuracy', 0.06366048), ('loss', 8.267054)])
Train round 6: OrderedDict([('rating_accuracy', 0.12331288), ('loss', 5.2693872)])
Train round 7: OrderedDict([('rating_accuracy', 0.14264487), ('loss', 5.1511016)])
Train round 8: OrderedDict([('rating_accuracy', 0.21046545), ('loss', 3.8246362)])
Train round 9: OrderedDict([('rating_accuracy', 0.21320973), ('loss', 3.303812)])
Train round 10: OrderedDict([('rating_accuracy', 0.21651311), ('loss', 3.4864292)])
Train round 11: OrderedDict([('rating_accuracy', 0.23476052), ('loss', 3.0105433)])
Train round 12: OrderedDict([('rating_accuracy', 0.21981856), ('loss', 3.1807854)])
Train round 13: OrderedDict([('rating_accuracy', 0.27683082), ('loss', 2.3382564)])
Train round 14: OrderedDict([('rating_accuracy', 0.26080742), ('loss', 2.7009728)])
Train round 15: OrderedDict([('rating_accuracy', 0.2733109), ('loss', 2.2993557)])
Train round 16: OrderedDict([('rating_accuracy', 0.29282996), ('loss', 2.5278995)])
Train round 17: OrderedDict([('rating_accuracy', 0.30204678), ('loss', 2.060092)])
Train round 18: OrderedDict([('rating_accuracy', 0.2940266), ('loss', 2.0976772)])
Train round 19: OrderedDict([('rating_accuracy', 0.3086304), ('loss', 2.0626144)])
Final Eval: OrderedDict([('loss', 1.9961331), ('rating_accuracy', 0.30322924)])

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

plt.plot(range(NUM_ROUNDS), train_losses)
plt.ylabel('Train Loss')
plt.xlabel('Round')
plt.title('Train Loss')
plt.show()

plt.plot(range(NUM_ROUNDS), train_accs)
plt.ylabel('Train Accuracy')
plt.xlabel('Round')
plt.title('Train Accuracy')
plt.show()

بي إن جي

بي إن جي

أخيرًا ، يمكننا حساب المقاييس على مجموعة اختبار غير مرئية عندما ننتهي من الضبط.

eval_metrics = evaluation_computation(state.model, tf_test_datasets)
print('Final Test:', eval_metrics['eval'])
Final Test: OrderedDict([('loss', 1.9566978), ('rating_accuracy', 0.30792442)])

مزيد من الاستكشافات

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

  • تأخذ التطبيقات النموذجية للمتوسطات الموحدة تمريرات محلية متعددة (فترات) فوق البيانات (بالإضافة إلى تمرير مرة واحدة على البيانات عبر دفعات متعددة). بالنسبة لإعادة الإعمار الفيدرالي ، قد نرغب في التحكم في عدد الخطوات بشكل منفصل لإعادة الإعمار والتدريب بعد إعادة الإعمار. تمرير dataset_split_fn حجة للتدريب والتقييم بناة حساب تمكن السيطرة على عدد من الخطوات والعهود على كل من مجموعات البيانات إعادة الإعمار وإعادة البناء في مرحلة ما بعد. كتمرين ، حاول أداء 3 فترات محلية من التدريب على إعادة الإعمار ، متوجًا بـ 50 خطوة وعصر محلي واحد من تدريب ما بعد إعادة الإعمار ، بحد أقصى 50 خطوة. تلميح: ستجد tff.learning.reconstruction.build_dataset_split_fn مفيدة. بمجرد القيام بذلك ، حاول ضبط هذه المعلمات الفائقة والمعلمات الأخرى ذات الصلة مثل معدلات التعلم وحجم الدفعة للحصول على نتائج أفضل.

  • السلوك الافتراضي للتدريب والتقييم الفيدرالي لإعادة الإعمار هو تقسيم البيانات المحلية للعملاء إلى النصف لكل من إعادة الإعمار وما بعد إعادة الإعمار. في الحالات التي يكون فيها العملاء لديهم القليل جدًا من البيانات المحلية ، قد يكون من المعقول إعادة استخدام البيانات لإعادة الإعمار وما بعد إعادة البناء لعملية التدريب فقط (وليس للتقييم ، سيؤدي ذلك إلى تقييم غير عادل). حاول إجراء هذا التغيير لعملية التدريب، وضمان dataset_split_fn لتقييم ما زالت تحافظ على إعادة البناء وإعادة الإعمار بعد منفصلتين البيانات. تلميح: tff.learning.reconstruction.simple_dataset_split_fn قد يكون مفيدا.

  • أعلاه، ونحن أنتجت tff.learning.Model من نموذج Keras باستخدام tff.learning.reconstruction.from_keras_model . يمكننا أيضا تنفيذ نموذج مخصص باستخدام TensorFlow نقية 2.0 تنفيذ واجهة نموذج . محاولة تعديل get_matrix_factorization_model لبناء وإرجاع الطبقة التي تمتد tff.learning.reconstruction.Model وتنفيذ أساليبها. تلميح: رمز مصدر tff.learning.reconstruction.from_keras_model مثالا تمديد tff.learning.reconstruction.Model الصف. الرجوع أيضا إلى تنفيذ نموذج مخصص في الصورة EMNIST تصنيف تعليمي لعملية مماثلة في مد tff.learning.Model .

  • في هذا البرنامج التعليمي ، قمنا بتحفيز التعلم الموحد المحلي جزئيًا في سياق عامل المصفوفة ، حيث يؤدي إرسال إرسالات المستخدم إلى الخادم إلى تسريب تفضيلات المستخدم بشكل تافه. يمكننا أيضًا تطبيق Federated Reconstruction في إعدادات أخرى كطريقة لتدريب المزيد من النماذج الشخصية (نظرًا لأن جزءًا من النموذج محلي تمامًا لكل مستخدم) مع تقليل الاتصال (نظرًا لعدم إرسال المعلمات المحلية إلى الخادم). بشكل عام ، باستخدام الواجهة المقدمة هنا ، يمكننا أن نأخذ أي نموذج متحد يتم تدريبه بشكل عام بشكل كامل وبدلاً من ذلك نقسم متغيراته إلى متغيرات عالمية ومتغيرات محلية. على سبيل المثال استكشافها في ورقة الاتحادية التعمير شخصي التنبؤ كلمة المقبل: هنا، كل مستخدم لديه مجموعة محلية خاصة بهم من التضمينات كلمة للكلمات الخروج من المفردات، وتمكين نموذج للعامية للمستخدمين التقاط "وتحقيق التخصيص دون اتصال إضافية. كتمرين ، حاول تنفيذ (كنموذج Keras أو نموذج TensorFlow 2.0 مخصص) نموذجًا مختلفًا للاستخدام مع Federated Reconstruction. اقتراح: تنفيذ نموذج تصنيف EMNIST مع تضمين مستخدم شخصي ، حيث يتم ربط تضمين المستخدم الشخصي بميزات صورة CNN قبل آخر طبقة كثيفة من النموذج. يمكنك إعادة استخدام الكثير من رمز من هذا البرنامج التعليمي (مثل UserEmbedding الطبقة) و صورة تصنيف البرنامج التعليمي .


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