การสร้างใหม่แบบรวมศูนย์สำหรับการแยกตัวประกอบเมทริกซ์

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

กวดวิชานี้จะสำรวจการเรียนรู้แบบ federated ท้องถิ่นบางส่วนที่พารามิเตอร์ลูกค้าบางส่วนจะไม่รวมอยู่บนเซิร์ฟเวอร์ สิ่งนี้มีประโยชน์สำหรับโมเดลที่มีพารามิเตอร์เฉพาะผู้ใช้ (เช่น โมเดลตัวประกอบเมทริกซ์) และสำหรับการฝึกอบรมในการตั้งค่าที่จำกัดการสื่อสาร เราสร้างบนแนวคิดที่นำมาใช้ใน การเรียนรู้สหพันธ์สำหรับภาพการจำแนกประเภท กวดวิชา; ในขณะที่การกวดวิชาที่เราแนะนำ APIs ระดับสูงใน tff.learning สำหรับการฝึกอบรมแบบ federated และการประเมินผล

เราเริ่มต้นด้วยการสร้างแรงจูงใจในการเรียนรู้แบบ federated ท้องถิ่นบางส่วนสำหรับ เมทริกซ์ตีนเป็ด เราอธิบายสหพันธ์บูรณะ ( กระดาษ , บล็อกโพสต์ ) ซึ่งเป็นขั้นตอนวิธีการปฏิบัติสำหรับการเรียนรู้แบบ federated ท้องถิ่นบางส่วนในระดับ เราเตรียมชุดข้อมูล 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; ผู้ใช้ Wikipedia Moshanin)

เมทริกซ์นี้โดยทั่วไปจะเบาบาง เนื่องจากผู้ใช้มักจะเห็นภาพยนตร์เพียงเล็กน้อยในชุดข้อมูล การส่งออกของเมทริกซ์ตีนเป็ดเป็นสองการฝึกอบรม: การ \(n \times k\) เมทริกซ์ \(U\) ตัวแทน \(k\)embeddings ใช้มิติสำหรับผู้ใช้แต่ละและ \(m \times k\) เมทริกซ์ \(I\) ตัวแทน \(k\)embeddings รายการมิติสำหรับแต่ละรายการ วัตถุประสงค์การฝึกอบรมที่ง่ายที่สุดคือเพื่อให้แน่ใจว่าผลิตภัณฑ์ที่จุดของผู้ใช้และรายการ embeddings มีการคาดการณ์ของการจัดอันดับที่สังเกต \(O\):

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

ซึ่งเทียบเท่ากับการลดความคลาดเคลื่อนกำลังสองเฉลี่ยระหว่างการให้คะแนนที่สังเกตได้และการให้คะแนนที่คาดการณ์โดยการนำผลคูณดอทของการฝังผู้ใช้และรายการที่เกี่ยวข้อง วิธีการแปลความหมายนี้ก็คือว่าเพื่อให้แน่ใจว่า \(R \approx UI^T\) สำหรับการจัดอันดับเป็นที่รู้จักกันเพราะฉะนั้น "เมทริกซ์ตีนเป็ด" หากทำให้เกิดความสับสน ไม่ต้องกังวล เราไม่จำเป็นต้องทราบรายละเอียดของการแยกตัวประกอบของเมทริกซ์สำหรับบทช่วยสอนที่เหลือ

การสำรวจข้อมูล MovieLens

เริ่มต้น Let 's โดยการโหลด 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 DataFrames ที่มีข้อมูลเรตติ้งและภาพยนตร์กัน

ratings_df, movies_df = load_movielens_data()

เราจะเห็นได้ว่าตัวอย่างการจัดประเภทแต่ละรายการมีคะแนนตั้งแต่ 1-5, UserID ที่สอดคล้องกัน, 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))

png

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()

png

ข้อมูลนี้ถูกแบ่งโดยธรรมชาติเป็นการให้คะแนนจากผู้ใช้ที่แตกต่างกัน ดังนั้นเราจึงคาดหวังความแตกต่างบางอย่างในข้อมูลระหว่างลูกค้า ด้านล่างเราแสดงประเภทภาพยนตร์ที่ได้รับการจัดอันดับโดยทั่วไปสำหรับผู้ใช้ที่แตกต่างกัน เราสามารถสังเกตความแตกต่างที่สำคัญระหว่างผู้ใช้

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 s แสดงข้อมูลของผู้ใช้แต่ละสำหรับใช้กับฉิบหาย

เราใช้สองฟังก์ชัน:

  • create_tf_datasets : ใช้เวลาการจัดอันดับของเรา DataFrame และผลิตรายการการใช้งานแยก tf.data.Dataset s
  • split_tf_datasets : รับรายการของชุดข้อมูลและแยกพวกเขาเข้าไปในรถไฟ / Val / การทดสอบโดยผู้ใช้ดังนั้น Val / ชุดทดสอบมีเพียงการให้คะแนนจากผู้ใช้ที่มองไม่เห็นในระหว่างการฝึก มักจะอยู่ในมาตรฐานตีนเป็ดส่วนกลางเมทริกซ์เราจริงแยกเพื่อให้ชุด Val / ทดสอบประกอบด้วยการจัดอันดับจะถือออกมาจากผู้ใช้เห็นตั้งแต่ผู้ใช้ที่มองไม่เห็นไม่ได้มี embeddings ผู้ใช้ ในกรณีของเรา เราจะเห็นในภายหลังว่าวิธีที่เราใช้เพื่อเปิดใช้งานการแยกตัวประกอบของเมทริกซ์ใน 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" โปรดทราบว่าเราไม่ต้องการ UserID เนื่องจากผู้ใช้แต่ละรายจะเห็นเฉพาะข้อมูลของตนเองเท่านั้น

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()

png

ตอนนี้เราได้โหลดและสำรวจข้อมูลแล้ว เราจะพูดถึงวิธีนำการแยกตัวประกอบของเมทริกซ์มาสู่การเรียนรู้แบบสหพันธรัฐ ระหว่างทาง เราจะกระตุ้นการเรียนรู้แบบสหพันธรัฐบางส่วน

การนำตัวประกอบเมทริกซ์มาสู่ FL

แม้ว่าการแยกตัวประกอบของเมทริกซ์จะถูกนำมาใช้ในการตั้งค่าแบบรวมศูนย์ แต่โดยทั่วไปมีความเกี่ยวข้องโดยเฉพาะอย่างยิ่งในการเรียนรู้แบบรวมศูนย์: การให้คะแนนของผู้ใช้อาจอยู่ในอุปกรณ์ไคลเอ็นต์ที่แยกจากกัน และเราอาจต้องการเรียนรู้การฝังและคำแนะนำสำหรับผู้ใช้และรายการโดยไม่ต้องรวมศูนย์ข้อมูล เนื่องจากผู้ใช้แต่ละรายมีการฝังผู้ใช้ที่สอดคล้องกัน จึงเป็นเรื่องปกติที่ลูกค้าแต่ละรายจะเก็บข้อมูลการฝังผู้ใช้ ซึ่งปรับขนาดได้ดีกว่าเซิร์ฟเวอร์ส่วนกลางที่จัดเก็บการฝังผู้ใช้ทั้งหมด

ข้อเสนอหนึ่งสำหรับการนำตัวประกอบเมทริกซ์มาสู่ FL มีดังนี้:

  1. ร้านค้าเซิร์ฟเวอร์และส่งรายการเมทริกซ์ \(I\) ให้กับลูกค้าตัวอย่างแต่ละรอบ
  2. ลูกค้าอัปเดตเมทริกซ์รายการและผู้ใช้ส่วนบุคคลของพวกเขาฝัง \(U_u\) ใช้ SGD กับวัตถุประสงค์ดังกล่าวข้างต้น
  3. การอัปเดต \(I\) มีการรวบรวมบนเซิร์ฟเวอร์ปรับปรุงสำเนาเซิร์ฟเวอร์ของ \(I\) สำหรับรอบต่อไป

วิธีการนี้เป็นเพียงบางส่วนในท้องถิ่นใช่หรือไม่เพราะเป็นพารามิเตอร์ลูกค้าบางคนจะไม่เคยรวบรวมโดยเซิร์ฟเวอร์ แม้ว่าวิธีการนี้จะน่าดึงดูดใจ แต่ลูกค้าจำเป็นต้องรักษาสถานะไว้ตลอดทั้งรอบ กล่าวคือ การฝังผู้ใช้ของพวกเขา อัลกอริธึมแบบรวมสถานะจะไม่ค่อยเหมาะสมสำหรับการตั้งค่า FL ข้ามอุปกรณ์: ในการตั้งค่าเหล่านี้ ขนาดประชากรมักจะมากกว่าจำนวนไคลเอนต์ที่เข้าร่วมในแต่ละรอบ และลูกค้ามักจะเข้าร่วมอย่างน้อยที่สุดหนึ่งครั้งในระหว่างกระบวนการฝึกอบรม นอกจากการพึ่งพารัฐที่อาจไม่ได้เริ่มต้นขั้นตอนวิธีการ stateful สามารถส่งผลในการลดประสิทธิภาพในการตั้งค่าอุปกรณ์ข้ามเนื่องจากรัฐได้รับเก่าเมื่อลูกค้าเป็นตัวอย่างบ่อย ที่สำคัญ ในการตั้งค่าการแยกตัวประกอบของเมทริกซ์ อัลกอริธึมแบบเก็บสถานะทำให้ไคลเอนต์ที่มองไม่เห็นทั้งหมดขาดการฝังผู้ใช้ที่ผ่านการฝึกอบรม และในการฝึกอบรมขนาดใหญ่ ผู้ใช้ส่วนใหญ่อาจมองไม่เห็น สำหรับข้อมูลเพิ่มเติมเกี่ยวแรงจูงใจสำหรับขั้นตอนวิธีการไร้สัญชาติในอุปกรณ์ข้าม FL, ดู วัง et al, 2021 วินาที 3.1.1 และ Reddi et al, 2020 ก.ล.ต. 5.1

สหพันธ์การบูรณะ ( สิงคาล et al. 2021 ) เป็นทางเลือกที่ไร้สัญชาติกับวิธีการดังกล่าวข้างต้น แนวคิดหลักคือแทนที่จะจัดเก็บการฝังผู้ใช้ข้ามรอบ ไคลเอนต์สร้างการฝังผู้ใช้ใหม่เมื่อจำเป็น เมื่อ FedRecon ถูกนำไปใช้กับการแยกตัวประกอบของเมทริกซ์ การฝึกอบรมจะดำเนินการดังนี้:

  1. ร้านค้าเซิร์ฟเวอร์และส่งรายการเมทริกซ์ \(I\) ให้กับลูกค้าตัวอย่างแต่ละรอบ
  2. ลูกค้าแต่ละคนค้าง \(I\) และรถไฟใช้การฝังตัวของพวกเขา \(U_u\) ใช้หนึ่งหรือมากกว่าขั้นตอนดอลลาร์สิงคโปร์ (ฟื้นฟู)
  3. ลูกค้าแต่ละคนค้าง \(U_u\) และรถไฟ \(I\) ใช้หนึ่งหรือมากกว่าขั้นตอนเหรียญสิงคโปร์
  4. การอัปเดต \(I\) มีการรวบรวมผู้ใช้ทั่วปรับปรุงสำเนาเซิร์ฟเวอร์ของ \(I\) สำหรับรอบต่อไป

แนวทางนี้ไม่ต้องการให้ลูกค้ารักษาสถานะไว้ตลอดทั้งรอบ ผู้เขียนยังแสดงให้เห็นในรายงานด้วยว่าวิธีนี้นำไปสู่การสร้างใหม่อย่างรวดเร็วของการฝังผู้ใช้สำหรับลูกค้าที่มองไม่เห็น (ข้อ 4.2 รูปที่ 3 และตารางที่ 1) ทำให้ลูกค้าส่วนใหญ่ที่ไม่ได้เข้าร่วมการฝึกอบรมมีแบบจำลองที่ได้รับการฝึกอบรม ซึ่งช่วยให้คำแนะนำสำหรับลูกค้าเหล่านี้ ดูสหพันธ์ฟื้นฟู โพสต์บล็อกของ Google 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)

คล้ายคลึงอินเตอร์เฟซสำหรับสหพันธ์ Averaging, อินเตอร์เฟซสำหรับสหพันธ์ฟื้นฟูคาดว่า 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 สำหรับการประเมินผล สิ่งเหล่านี้จำเป็นสำหรับการสร้างการฝึกอบรมและการคำนวณประเมินผล

เราจะใช้ Mean Squared Error เป็นการสูญเสียตามที่กล่าวไว้ข้างต้น สำหรับการประเมิน เราจะใช้ความแม่นยำในการให้คะแนน (เมื่อผลิตภัณฑ์ดอทที่คาดการณ์ไว้ของโมเดลถูกปัดเศษเป็นจำนวนเต็มที่ใกล้ที่สุด ความถี่จะตรงกับระดับฉลากบ่อยเพียงใด)

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

การฝึกอบรมและการประเมินผล

ตอนนี้เรามีทุกอย่างที่เราต้องการเพื่อกำหนดกระบวนการฝึกอบรม หนึ่งความแตกต่างที่สำคัญจาก อินเตอร์เฟซสำหรับสหพันธ์ Averaging คือว่าตอนนี้เราผ่านใน reconstruction_optimizer_fn ซึ่งจะใช้เมื่อมีการฟื้นฟูพารามิเตอร์ท้องถิ่น (ในกรณีของเรา embeddings ผู้ใช้) มันเป็นเรื่องทั่วไปที่เหมาะสมที่จะใช้ 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\) ใช้หนึ่งหรือมากกว่าขั้นตอนดอลลาร์สิงคโปร์ (ฟื้นฟู)
  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()

png

png

สุดท้าย เราสามารถคำนวณเมตริกในชุดการทดสอบที่มองไม่เห็นเมื่อเราปรับแต่งเสร็จแล้ว

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

การสำรวจเพิ่มเติม

ทำได้ดีมากในการกรอกสมุดบันทึกนี้ เราขอแนะนำแบบฝึกหัดต่อไปนี้เพื่อสำรวจการเรียนรู้แบบสหพันธรัฐในพื้นที่บางส่วนเพิ่มเติม โดยเรียงลำดับคร่าวๆ โดยการเพิ่มความยาก:

  • การใช้งานทั่วไปของ Federated Averaging จะใช้การส่งผ่านข้อมูลในพื้นที่ (ยุค) หลายครั้ง (นอกเหนือจากการรับส่งข้อมูลครั้งเดียวในหลายแบตช์) สำหรับ Federated Reconstruction เราอาจต้องการควบคุมจำนวนขั้นตอนแยกกันสำหรับการฝึกอบรมการสร้างใหม่และหลังการสร้างใหม่ ผ่าน dataset_split_fn โต้แย้งกับการฝึกอบรมและการคำนวณการประเมินผลผู้สร้างที่ช่วยให้การควบคุมจำนวนของขั้นตอนและ epochs ทั้งการฟื้นฟูและการโพสต์ฟื้นฟูชุดข้อมูล ในแบบฝึกหัด ให้ลองทำการฝึกฟื้นฟูร่างกาย 3 ช่วงเวลา โดยต่อยอดที่ 50 ขั้นตอนและ 1 ช่วงเวลาของการฝึกหลังการฟื้นฟูในพื้นที่ โดยต่อยอดที่ 50 ขั้นตอน คำแนะนำ: คุณจะพบ tff.learning.reconstruction.build_dataset_split_fn ที่เป็นประโยชน์ เมื่อคุณทำเสร็จแล้ว ให้ลองปรับไฮเปอร์พารามิเตอร์เหล่านี้และพารามิเตอร์อื่นๆ ที่เกี่ยวข้อง เช่น อัตราการเรียนรู้และขนาดแบทช์เพื่อให้ได้ผลลัพธ์ที่ดีขึ้น

  • พฤติกรรมเริ่มต้นของการฝึกอบรมและการประเมิน Federated Reconstruction คือการแบ่งข้อมูลในเครื่องของลูกค้าออกเป็นครึ่งหนึ่งสำหรับการสร้างใหม่และหลังการสร้างใหม่แต่ละครั้ง ในกรณีที่ลูกค้ามีข้อมูลในพื้นที่น้อยมาก อาจมีเหตุผลที่จะนำข้อมูลกลับมาใช้ใหม่เพื่อสร้างใหม่และหลังการสร้างใหม่สำหรับกระบวนการฝึกอบรมเท่านั้น (ไม่ใช่สำหรับการประเมิน การดำเนินการนี้จะนำไปสู่การประเมินที่ไม่เป็นธรรม) ลองทำการเปลี่ยนแปลงนี้สำหรับกระบวนการฝึกอบรม, การสร้างความมั่นใจ 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 ในการตั้งค่าอื่น ๆ เพื่อฝึกโมเดลส่วนบุคคลมากขึ้น (เนื่องจากส่วนหนึ่งของโมเดลเป็นแบบเฉพาะสำหรับผู้ใช้แต่ละราย) ในขณะที่ลดการสื่อสาร (เนื่องจากพารามิเตอร์ในเครื่องจะไม่ถูกส่งไปยังเซิร์ฟเวอร์) โดยทั่วไป การใช้อินเทอร์เฟซที่นำเสนอในที่นี้ เราสามารถใช้โมเดลรวมใดๆ ที่โดยทั่วไปแล้วจะได้รับการฝึกอบรมอย่างเต็มรูปแบบทั่วโลก และแทนที่จะแบ่งพาร์ติชั่นของตัวแปรออกเป็นตัวแปรส่วนกลางและตัวแปรในเครื่อง ตัวอย่างการสำรวจใน กระดาษสหพันธ์การฟื้นฟู คือการทำนายคำถัดไปส่วนบุคคล: นี่ผู้ใช้แต่ละคนมีชุดท้องถิ่นของตัวเองของ embeddings คำคำออกจากคำศัพท์, การเปิดใช้งานแบบจำลองเพื่อสแลงผู้ใช้จับภาพและประสบความสำเร็จส่วนบุคคลโดยไม่ต้องสื่อสารเพิ่มเติม ในแบบฝึกหัด ให้ลองใช้โมเดลอื่น (ในรูปแบบ Keras หรือโมเดล TensorFlow 2.0 แบบกำหนดเอง) สำหรับใช้กับ Federated Reconstruction คำแนะนำ: ใช้โมเดลการจัดหมวดหมู่ EMNIST ด้วยการฝังผู้ใช้ส่วนบุคคล โดยที่การฝังผู้ใช้ส่วนบุคคลจะเชื่อมต่อกับคุณลักษณะรูปภาพของ CNN ก่อนเลเยอร์ Dense สุดท้ายของโมเดล คุณสามารถนำมาใช้ใหม่มากรหัสจากการกวดวิชานี้ (เช่น UserEmbedding ชั้น) และ ภาพการจำแนกประเภทกวดวิชา


หากคุณยังคงมองหาข้อมูลเพิ่มเติมเกี่ยวกับการเรียนรู้แบบ federated ท้องถิ่นบางส่วนตรวจสอบ กระดาษสหพันธ์ฟื้นฟู และ โอเพนซอร์สโค้ดการทดสอบ