Lưu ngày! Google I / O hoạt động trở lại từ ngày 18 đến 20 tháng 5 Đăng ký ngay
Trang này được dịch bởi Cloud Translation API.
Switch to English

Thuật toán liên kết tùy chỉnh, Phần 1: Giới thiệu về lõi liên kết

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Hướng dẫn này là phần đầu tiên của loạt bài gồm hai phần trình bày cách triển khai các loại thuật toán liên kết tùy chỉnh trong TensorFlow Federated (TFF) bằng cách sử dụng Federated Core (FC) - một tập hợp các giao diện cấp thấp hơn đóng vai trò là nền tảng chúng tôi đã triển khai lớp Học liên kết (FL) .

Phần đầu tiên này mang tính khái niệm nhiều hơn; chúng tôi giới thiệu một số khái niệm chính và lập trình trừu tượng được sử dụng trong TFF và chúng tôi chứng minh việc sử dụng chúng trên một ví dụ rất đơn giản với một dãy phân tán các cảm biến nhiệt độ. Trong phần thứ hai của loạt bài này , chúng tôi sử dụng các cơ chế mà chúng tôi giới thiệu ở đây để triển khai một phiên bản đơn giản của các thuật toán đánh giá và đào tạo liên hợp. Tiếp theo, chúng tôi khuyến khích bạn nghiên cứu việc triển khai tính trung bình được liên kết trong tff.learning .

Đến cuối loạt bài này, bạn sẽ có thể nhận ra rằng các ứng dụng của Federated Core không nhất thiết phải giới hạn trong việc học. Các phần tóm tắt lập trình mà chúng tôi cung cấp khá chung chung và có thể được sử dụng, ví dụ: để triển khai phân tích và các loại tính toán tùy chỉnh khác trên dữ liệu phân tán.

Mặc dù hướng dẫn này được thiết kế khép kín, chúng tôi khuyến khích bạn đọc trước các hướng dẫn về phân loại hình ảnhtạo văn bản để có phần giới thiệu cấp cao hơn và nhẹ nhàng hơn về khung công tác TensorFlow Federated và các API học tập liên kết ( tff.learning ), như nó sẽ giúp bạn đưa các khái niệm mà chúng tôi mô tả ở đây vào ngữ cảnh.

Mục đích sử dụng

Tóm lại, Federated Core (FC) là một môi trường phát triển giúp nó có thể diễn đạt một cách gọn nhẹ logic chương trình kết hợp mã TensorFlow với các toán tử truyền thông phân tán, chẳng hạn như những toán tử được sử dụng trong Liên kết trung bình - tính toán tổng, trung bình phân tán và các loại khác tổng hợp phân tán trên một tập hợp các thiết bị khách trong hệ thống, các mô hình và thông số phát sóng tới các thiết bị đó, v.v.

Bạn có thể biết về tf.contrib.distribute và một câu hỏi tự nhiên cần đặt ra vào thời điểm này có thể là: framework này khác nhau ở những điểm nào? Xét cho cùng, cả hai framework đều cố gắng làm cho các phép tính TensorFlow được phân phối.

Một cách để suy nghĩ về nó là, trong khi mục tiêu đã nêu của tf.contrib.distributecho phép người dùng sử dụng các mô hình và mã đào tạo hiện có với những thay đổi tối thiểu để cho phép đào tạo phân tán và tập trung nhiều vào cách tận dụng cơ sở hạ tầng phân tán để làm cho mã đào tạo hiện có hiệu quả hơn, mục tiêu của Lõi liên kết của TFF là cung cấp cho các nhà nghiên cứu và người thực hành quyền kiểm soát rõ ràng đối với các mẫu truyền thông phân tán cụ thể mà họ sẽ sử dụng trong hệ thống của mình. Trọng tâm trong FC là cung cấp một ngôn ngữ linh hoạt và có thể mở rộng để thể hiện các thuật toán luồng dữ liệu phân tán, chứ không phải là một tập hợp cụ thể các khả năng đào tạo phân tán được thực hiện.

Một trong những đối tượng mục tiêu chính cho API FC của TFF là các nhà nghiên cứu và học viên có thể muốn thử nghiệm các thuật toán học tập liên hợp mới và đánh giá hậu quả của các lựa chọn thiết kế tinh vi ảnh hưởng đến cách thức điều phối luồng dữ liệu trong hệ thống phân tán. mà không bị sa lầy bởi các chi tiết triển khai hệ thống. Mức độ trừu tượng mà FC API đang hướng tới gần tương ứng với mã giả mà người ta có thể sử dụng để mô tả cơ chế của thuật toán học liên hợp trong một ấn phẩm nghiên cứu - dữ liệu nào tồn tại trong hệ thống và cách nó được chuyển đổi, nhưng không giảm xuống mức trao đổi tin nhắn mạng điểm-điểm riêng lẻ.

TFF nói chung là nhắm mục tiêu các tình huống trong đó dữ liệu được phân phối và phải duy trì như vậy, ví dụ, vì lý do bảo mật và nơi thu thập tất cả dữ liệu tại một địa điểm tập trung có thể không phải là một lựa chọn khả thi. Điều này có nghĩa là về việc triển khai các thuật toán học máy đòi hỏi mức độ kiểm soát rõ ràng hơn, so với các tình huống trong đó tất cả dữ liệu có thể được tích lũy ở một vị trí tập trung tại một trung tâm dữ liệu.

Trước khi chúng ta bắt đầu

Trước khi chúng tôi đi sâu vào mã, hãy thử chạy ví dụ "Hello World" sau đây để đảm bảo rằng môi trường của bạn được thiết lập chính xác. Nếu nó không hoạt động, vui lòng tham khảo Hướng dẫn cài đặt để được hướng dẫn.

!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!'

Dữ liệu liên kết

Một trong những tính năng khác biệt của TFF là nó cho phép bạn thể hiện một cách gọn nhẹ các tính toán dựa trên TensorFlow trên dữ liệu liên kết . Chúng tôi sẽ sử dụng thuật ngữ dữ liệu được liên kết trong hướng dẫn này để chỉ tập hợp các mục dữ liệu được lưu trữ trên một nhóm thiết bị trong hệ thống phân tán. Ví dụ: các ứng dụng chạy trên thiết bị di động có thể thu thập dữ liệu và lưu trữ cục bộ mà không cần tải lên vị trí tập trung. Hoặc, một loạt các cảm biến phân tán có thể thu thập và lưu trữ các chỉ số nhiệt độ tại vị trí của chúng.

Dữ liệu liên kết giống như những dữ liệu trong các ví dụ trên được coi là công dân hạng nhất trong TFF, tức là, chúng có thể xuất hiện dưới dạng các tham số và kết quả của các hàm, và chúng có các kiểu. Để củng cố khái niệm này, chúng tôi sẽ đề cập đến các tập dữ liệu được liên kết dưới dạng các giá trị được liên kết hoặc dưới dạng giá trị của các kiểu được liên kết .

Điểm quan trọng cần hiểu là chúng tôi đang mô hình hóa toàn bộ bộ sưu tập các mục dữ liệu trên tất cả các thiết bị (ví dụ: toàn bộ số đọc nhiệt độ bộ sưu tập từ tất cả các cảm biến trong một mảng phân tán) dưới dạng một giá trị liên kết duy nhất.

Ví dụ: đây là cách người ta sẽ xác định trong TFF loại float liên kết được lưu trữ bởi một nhóm thiết bị khách. Một tập hợp các số đọc nhiệt độ hiện thực hóa trên một loạt các cảm biến phân tán có thể được mô hình hóa dưới dạng giá trị của loại liên hợp này.

federated_float_on_clients = tff.type_at_clients(tf.float32)

Nói một cách tổng quát hơn, một loại liên kết trong TFF được xác định bằng cách chỉ định loại T của các thành phần thành viên của nó - các mục dữ liệu nằm trên các thiết bị riêng lẻ và nhóm G thiết bị mà các giá trị liên kết của loại này được lưu trữ (cộng với một phần ba, thông tin tùy chọn mà chúng tôi sẽ đề cập ngay sau đây). Chúng tôi gọi nhóm thiết bị G lưu trữ giá trị được liên kết là vị trí của giá trị. Do đó, tff.CLIENTS là một ví dụ về một vị trí.

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

Một loại liên kết với các thành phần thành viên T và vị trí G có thể được trình bày ngắn gọn dưới dạng {T}@G , như được hiển thị bên dưới.

str(federated_float_on_clients)
'{float32}@CLIENTS'

Dấu ngoặc nhọn {} trong ký hiệu ngắn gọn này đóng vai trò như một lời nhắc nhở rằng các thành phần thành viên (các mục dữ liệu trên các thiết bị khác nhau) có thể khác nhau, như bạn mong đợi, chẳng hạn như các bài đọc cảm biến nhiệt độ, vì vậy các khách hàng là một nhóm cùng lưu trữ nhiều -Thiết của T -typed mục mà cùng nhau tạo thành giá trị liên.

Điều quan trọng cần lưu ý là các thành viên của một giá trị liên thường đục để các lập trình viên, tức là một giá trị liên không nên được coi là một đơn giản dict keyed bởi một định danh của một thiết bị trong hệ thống - những giá trị này được dùng để chỉ được chuyển đổi chung bởi các toán tử liên hợp đại diện một cách trừu tượng cho các loại giao thức truyền thông phân tán (chẳng hạn như tập hợp). Nếu điều này nghe có vẻ quá trừu tượng, đừng lo lắng - chúng tôi sẽ quay lại vấn đề này ngay sau đó và chúng tôi sẽ minh họa nó bằng các ví dụ cụ thể.

Các loại liên kết trong TFF có hai loại: loại mà các thành phần thành viên của một giá trị liên kết có thể khác nhau (như vừa thấy ở trên) và loại mà chúng được biết là tất cả đều bình đẳng. Điều này được kiểm soát bởi tham số all_equal thứ ba, tùy chọn trong tff.FederatedType tạo tff.FederatedType (mặc định là False ).

federated_float_on_clients.all_equal
False

Loại liên kết có vị trí G trong đó tất cả các thành phần có kiểu T được biết là bằng nhau có thể được biểu diễn gọn gàng dưới dạng T@G (trái ngược với {T}@G , nghĩa là với dấu ngoặc nhọn được thả xuống để phản ánh thực tế là tập hợp nhiều thành phần cấu thành bao gồm một mục duy nhất).

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

Một ví dụ về giá trị liên kết của loại như vậy có thể phát sinh trong các tình huống thực tế là một siêu thông số (chẳng hạn như tốc độ học tập, định mức cắt, v.v.) đã được máy chủ truyền phát tới một nhóm thiết bị tham gia đào tạo liên kết.

Một ví dụ khác là một tập hợp các tham số cho mô hình học máy được đào tạo trước tại máy chủ, sau đó được truyền tới một nhóm thiết bị khách, nơi chúng có thể được cá nhân hóa cho từng người dùng.

Ví dụ, giả sử chúng ta có một cặp tham số float32 ab cho mô hình hồi quy tuyến tính một chiều đơn giản. Chúng ta có thể xây dựng kiểu (không liên kết) của các mô hình như vậy để sử dụng trong TFF như sau. Dấu ngoặc nhọn <> trong chuỗi kiểu in là một ký hiệu TFF nhỏ gọn cho các bộ giá trị được đặt tên hoặc không được đặt tên.

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

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

Lưu ý rằng chúng tôi chỉ xác định loại dtype ở trên. Các loại không vô hướng cũng được hỗ trợ. Trong đoạn mã trên, tf.float32 là ký hiệu tắt cho tff.TensorType(dtype=tf.float32, shape=[]) .

Khi mô hình này được phát cho khách hàng, loại giá trị liên kết kết quả có thể được biểu diễn như hình dưới đây.

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

Theo đối xứng với float liên kết ở trên, chúng tôi sẽ gọi một loại như vậy là một tuple liên kết . Nói chung hơn, chúng tôi thường sử dụng thuật ngữ XYZ được liên kết để chỉ một giá trị được liên kết trong đó các thành phần thành viên giống như XYZ . Do đó, chúng ta sẽ nói về những thứ như bộ giá trị liên kết , chuỗi liên kết , mô hình liên kết , v.v.

Bây giờ, quay trở lại float32@CLIENTS - trong khi nó có vẻ được sao chép trên nhiều thiết bị, nó thực sự là một float32 duy nhất, vì tất cả các thành viên đều giống nhau. Nói chung, bạn có thể nghĩ về bất kỳ kiểu liên kết ngang bằng nào , tức là một trong các dạng T@G , giống như là đồng hình với kiểu T không liên kết, vì trong cả hai trường hợp, thực tế chỉ có một mục duy nhất (mặc dù có khả năng được sao chép) thuộc loại T

Với sự đẳng cấu giữa TT@G , bạn có thể tự hỏi mục đích gì, nếu có, các loại sau có thể phục vụ mục đích gì. Đọc tiếp.

Vị trí

Tổng quan thiết kế

Trong phần trước, chúng tôi đã giới thiệu khái niệm về vị trí - các nhóm người tham gia hệ thống có thể cùng lưu trữ một giá trị được liên kết và chúng tôi đã chứng minh việc sử dụng tff.CLIENTS như một đặc tả mẫu về một vị trí.

Để giải thích lý do tại sao khái niệm về vị trí là cơ bản đến mức chúng tôi cần kết hợp nó vào hệ thống loại TFF, hãy nhớ lại những gì chúng tôi đã đề cập ở phần đầu của hướng dẫn này về một số mục đích sử dụng của TFF.

Mặc dù trong hướng dẫn này, bạn sẽ chỉ thấy mã TFF được thực thi cục bộ trong môi trường mô phỏng, mục tiêu của chúng tôi là TFF cho phép viết mã mà bạn có thể triển khai để thực thi trên các nhóm thiết bị vật lý trong hệ thống phân tán, có khả năng bao gồm thiết bị di động hoặc thiết bị nhúng chạy Android. Mỗi thiết bị trong số đó sẽ nhận được một bộ hướng dẫn riêng biệt để thực thi cục bộ, tùy thuộc vào vai trò của nó trong hệ thống (thiết bị người dùng cuối, bộ điều phối tập trung, lớp trung gian trong kiến ​​trúc nhiều tầng, v.v.). Điều quan trọng là có thể suy luận về việc tập hợp con thiết bị nào thực thi mã nào và nơi các phần dữ liệu khác nhau có thể hiện thực hóa về mặt vật lý.

Điều này đặc biệt quan trọng khi xử lý, ví dụ: dữ liệu ứng dụng trên thiết bị di động. Vì dữ liệu là riêng tư và có thể nhạy cảm, chúng tôi cần khả năng xác minh tĩnh rằng dữ liệu này sẽ không bao giờ rời khỏi thiết bị (và chứng minh sự thật về cách dữ liệu đang được xử lý). Các thông số kỹ thuật về vị trí là một trong những cơ chế được thiết kế để hỗ trợ điều này.

TFF đã được thiết kế như một môi trường lập trình lấy dữ liệu làm trung tâm và như vậy, không giống như một số khuôn khổ hiện có tập trung vào các hoạt động và nơi các hoạt động đó có thể chạy , TFF tập trung vào dữ liệu , nơi dữ liệu đó hiện thực hóa và cách nó được chuyển đổi . Do đó, vị trí được mô hình hóa như một thuộc tính của dữ liệu trong TFF, chứ không phải là một thuộc tính của các hoạt động trên dữ liệu. Thật vậy, như bạn sắp thấy trong phần tiếp theo, một số hoạt động TFF trải dài khắp các vị trí và chạy "trong mạng", có thể nói, thay vì được thực thi bởi một máy hoặc một nhóm máy.

Việc thể hiện loại giá trị nhất định là T@G hoặc {T}@G (thay vì chỉ T ) đưa ra các quyết định về vị trí dữ liệu một cách rõ ràng và cùng với phân tích tĩnh về các chương trình được viết bằng TFF, nó có thể đóng vai trò là nền tảng để cung cấp đảm bảo quyền riêng tư chính thức cho dữ liệu nhạy cảm trên thiết bị.

Tuy nhiên, một điều quan trọng cần lưu ý tại thời điểm này là mặc dù chúng tôi khuyến khích người dùng TFF nói rõ về các nhóm thiết bị tham gia lưu trữ dữ liệu (vị trí), lập trình viên sẽ không bao giờ xử lý dữ liệu thô hoặc danh tính của từng người tham gia .

(Lưu ý: Mặc dù nó nằm ngoài phạm vi của hướng dẫn này, chúng ta nên đề cập rằng có một ngoại lệ đáng chú ý ở trên, một toán tử tff.federated_collect được dự định là một nguyên thủy cấp thấp, chỉ dành cho các tình huống chuyên biệt. Việc sử dụng nó rõ ràng trong các tình huống có thể tránh được thì không nên vì nó có thể hạn chế các ứng dụng có thể có trong tương lai. Ví dụ: nếu trong quá trình phân tích tĩnh, chúng tôi xác định rằng một máy tính sử dụng các cơ chế cấp thấp như vậy, chúng tôi có thể không cho phép nó truy cập vào một số các loại dữ liệu.)

Trong phần nội dung của mã TFF, theo thiết kế, không có cách nào để liệt kê các thiết bị tạo thành nhóm được đại diện bởi tff.CLIENTS , hoặc để thăm dò sự tồn tại của một thiết bị cụ thể trong nhóm. Không có khái niệm về thiết bị hoặc nhận dạng khách hàng ở bất kỳ đâu trong API Liên kết lõi, tập hợp cơ bản của các kiến ​​trúc trừu tượng hoặc cơ sở hạ tầng thời gian chạy cốt lõi mà chúng tôi cung cấp để hỗ trợ mô phỏng. Tất cả logic tính toán bạn viết sẽ được thể hiện dưới dạng các hoạt động trên toàn bộ nhóm khách hàng.

Nhớ lại ở đây những gì chúng ta đã đề cập trước đó về các giá trị của các kiểu liên kết không giống như Python dict , ở chỗ người ta không thể chỉ đơn giản liệt kê các thành phần thành viên của chúng. Hãy nghĩ về các giá trị mà logic chương trình TFF của bạn thao tác như được liên kết với các vị trí (nhóm), thay vì với những người tham gia riêng lẻ.

Các vị trí cũng được thiết kế để trở thành công dân hạng nhất trong TFF và có thể xuất hiện dưới dạng các tham số và kết quả của một loại placement (được biểu thị bằng tff.PlacementType trong API). Trong tương lai, chúng tôi có kế hoạch cung cấp nhiều toán tử khác nhau để chuyển đổi hoặc kết hợp các vị trí, nhưng điều này nằm ngoài phạm vi của hướng dẫn này. Còn bây giờ, nó cũng đủ để nghĩ về placement như một đục nguyên thủy được xây dựng-in gõ TFF, tương tự như cách intbool được đục sẵn trong các loại bằng Python, với tff.CLIENTS là một chữ liên tục của loại hình này, không khác gì 1 là một ký tự hằng của kiểu int .

Chỉ định vị trí

TFF cung cấp hai ký tự vị trí cơ bản, tff.CLIENTStff.SERVER , để giúp dễ dàng diễn đạt nhiều tình huống thực tế phong phú được mô hình hóa tự nhiên dưới dạng kiến ​​trúc máy khách-máy chủ, với nhiều thiết bị khách (điện thoại di động, thiết bị nhúng, cơ sở dữ liệu phân tán) , cảm biến, v.v.) được điều phối bởi một điều phối viên máy chủ tập trung duy nhất. TFF được thiết kế để cũng hỗ trợ các vị trí tùy chỉnh, nhiều nhóm khách hàng, nhiều tầng và các kiến ​​trúc phân tán tổng quát hơn, nhưng thảo luận về chúng nằm ngoài phạm vi của hướng dẫn này.

TFF không quy định tff.CLIENTS hay tff.SERVER thực sự đại diện cho cái gì.

Cụ thể, tff.SERVER có thể là một thiết bị vật lý đơn lẻ (thành viên của một nhóm singleton), nhưng nó cũng có thể là một nhóm bản sao trong một cụm máy trạng thái có khả năng chịu lỗi đang chạy - chúng tôi không tạo bất kỳ kiến ​​trúc đặc biệt nào các giả định. Thay vào đó, chúng tôi sử dụng bit all_equal được đề cập trong phần trước để thể hiện thực tế rằng chúng tôi thường chỉ xử lý một mục dữ liệu duy nhất tại máy chủ.

Tương tự như vậy, tff.CLIENTS trong một số ứng dụng có thể đại diện cho tất cả khách hàng trong hệ thống - những gì trong bối cảnh học tập liên đôi khi chúng ta gọi là dân số, nhưng ví dụ, trong việc triển khai sản xuất Federated trung bình , nó có thể đại diện cho một nhóm - một tập hợp con của các khách hàng được chọn để tham gia trong một vòng đào tạo cụ thể. Các vị trí được xác định trừu tượng có ý nghĩa cụ thể khi một phép tính mà chúng xuất hiện được triển khai để thực thi (hoặc đơn giản được gọi như một hàm Python trong môi trường mô phỏng, như được minh họa trong hướng dẫn này). Trong mô phỏng cục bộ của chúng tôi, nhóm khách hàng được xác định bởi dữ liệu liên kết được cung cấp làm đầu vào.

Tính toán liên hợp

Khai báo các phép tính liên hợp

TFF được thiết kế như một môi trường lập trình chức năng được đánh máy mạnh mẽ hỗ trợ phát triển mô-đun.

Đơn vị cơ bản của thành phần trong TFF là tính toán liên hợp - một phần logic có thể chấp nhận các giá trị liên kết làm đầu vào và trả về các giá trị liên kết làm đầu ra. Đây là cách bạn có thể xác định một phép tính tính trung bình của nhiệt độ được báo cáo bởi mảng cảm biến từ ví dụ trước của chúng tôi.

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

Nhìn vào đoạn mã trên, tại thời điểm này, bạn có thể hỏi - chưa có cấu trúc trang trí nào để xác định các đơn vị có thể kết hợp như tf.function trong TensorFlow, và nếu vậy, tại sao lại giới thiệu một cái khác, và nó khác nhau như thế nào?

Câu trả lời ngắn gọn là mã được tạo bởi trình bao bọc tff.federated_computation không phải là TensorFlow, cũng không phải là Python - đó là một đặc điểm kỹ thuật của một hệ thống phân tán trong một ngôn ngữ keo độc lập với nền tảng nội bộ. Tại thời điểm này, điều này chắc chắn sẽ nghe có vẻ khó hiểu, nhưng hãy lưu ý cách giải thích trực quan này về phép tính liên hợp như một đặc điểm kỹ thuật trừu tượng của một hệ thống phân tán trong tâm trí. Chúng tôi sẽ giải thích nó trong một phút.

Đầu tiên, chúng ta hãy chơi với định nghĩa một chút. Các phép tính TFF thường được mô hình hóa dưới dạng các hàm - có hoặc không có tham số, nhưng với các ký hiệu kiểu được xác định rõ ràng. Bạn có thể in chữ ký kiểu của một phép tính bằng cách truy vấn thuộc tính type_signature của nó, như được hiển thị bên dưới.

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

Chữ ký kiểu cho chúng ta biết rằng phép tính chấp nhận một bộ sưu tập các bài đọc cảm biến khác nhau trên các thiết bị khách và trả về một mức trung bình duy nhất trên máy chủ.

Trước khi chúng ta đi xa hơn, hãy suy nghĩ về điều này trong một phút - đầu vào và đầu ra của phép tính này ở những nơi khác nhau (trên CLIENTS so với trên SERVER ). Nhớ lại những gì chúng ta đã nói trong phần trước về các vị trí về cách các hoạt động TFF có thể trải dài qua các vị trí và chạy trong mạng cũng như những gì chúng ta vừa nói về các phép tính liên hợp như thể hiện các đặc tả trừu tượng của hệ thống phân tán. Chúng tôi vừa xác định một tính toán như vậy - một hệ thống phân tán đơn giản, trong đó dữ liệu được sử dụng tại các thiết bị khách và kết quả tổng hợp xuất hiện tại máy chủ.

Trong nhiều trường hợp thực tế, các tính toán đại diện cho các tác vụ cấp cao nhất sẽ có xu hướng chấp nhận đầu vào của chúng và báo cáo kết quả đầu ra của chúng tại máy chủ - điều này phản ánh ý tưởng rằng các phép tính có thể được kích hoạt bởi các truy vấn bắt nguồn và kết thúc trên máy chủ.

Tuy nhiên, FC API không áp đặt giả định này và nhiều khối xây dựng mà chúng tôi sử dụng nội bộ (bao gồm nhiều toán tử tff.federated_... mà bạn có thể tìm thấy trong API) có đầu vào và đầu ra với các vị trí riêng biệt, vì vậy, nói chung, bạn nên không nghĩ về một phép tính liên hợp như một cái gì đó chạy trên máy chủ hoặc được thực thi bởi một máy chủ . Máy chủ chỉ là một loại người tham gia trong tính toán liên hợp. Khi suy nghĩ về cơ chế của những tính toán như vậy, tốt nhất bạn nên luôn mặc định theo quan điểm toàn mạng toàn cầu, thay vì quan điểm của một điều phối viên tập trung duy nhất.

Nói chung, các ký hiệu kiểu chức năng được biểu diễn gọn gàng dưới dạng (T -> U) cho các kiểu TU của đầu vào và đầu ra tương ứng. Loại tham số chính thức (trong trường hợp này là sensor_readings ) được chỉ định làm đối số cho trình trang trí. Bạn không cần chỉ định loại kết quả - nó được xác định tự động.

Mặc dù TFF cung cấp các hình thức đa hình hạn chế, các lập trình viên được khuyến khích nên trình bày rõ ràng về các loại dữ liệu mà họ làm việc, vì điều đó làm cho việc hiểu, gỡ lỗi và xác minh chính thức các thuộc tính mã của bạn dễ dàng hơn. Trong một số trường hợp, chỉ định rõ ràng các kiểu là một yêu cầu (ví dụ: các phép tính đa hình hiện không thể thực thi trực tiếp).

Thực thi các phép tính liên hợp

Để hỗ trợ phát triển và gỡ lỗi, TFF cho phép bạn trực tiếp gọi các phép tính được định nghĩa theo cách này dưới dạng các hàm Python, như được hiển thị bên dưới. Trong trường hợp tính toán mong đợi một giá trị của kiểu liên kết với bit all_equal được đặt thành False , bạn có thể cấp dữ liệu đó dưới dạng list thuần túy bằng Python và đối với các loại liên kết có bit all_equal được đặt thành True , bạn có thể chỉ cần cấp dữ liệu trực tiếp (đơn) thành viên cấu thành. Đây cũng là cách kết quả được thông báo lại cho bạn.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

Khi chạy các tính toán như thế này trong chế độ mô phỏng, bạn hoạt động như một người quan sát bên ngoài với chế độ xem toàn hệ thống, người có khả năng cung cấp đầu vào và tiêu thụ đầu ra tại bất kỳ vị trí nào trong mạng, như thực tế là trường hợp ở đây - bạn đã cung cấp các giá trị máy khách ở đầu vào và sử dụng kết quả máy chủ.

Bây giờ, hãy quay lại một ghi chú mà chúng ta đã thực hiện trước đó về trình trang trí tff.federated_computation phát ra mã bằng ngôn ngữ keo . Mặc dù logic của các phép tính TFF có thể được thể hiện dưới dạng các hàm thông thường trong Python (bạn chỉ cần trang trí chúng bằng tff.federated_computation như chúng tôi đã làm ở trên) và bạn có thể trực tiếp gọi chúng bằng các đối số Python giống như bất kỳ hàm Python nào khác trong này notebook, đằng sau hậu trường, như chúng tôi đã lưu ý trước đó, các tính toán TFF thực sự không phải là Python.

Ý chúng tôi muốn nói ở đây là khi trình thông dịch Python gặp một hàm được trang trí bằng tff.federated_computation , nó sẽ theo dõi các câu lệnh trong phần thân của hàm này một lần (tại thời điểm định nghĩa) và sau đó xây dựng một biểu diễn tuần tự của logic của tính toán để sử dụng trong tương lai - liệu để thực thi, hoặc được kết hợp như một thành phần phụ vào một phép tính khác.

Bạn có thể xác minh điều này bằng cách thêm một câu lệnh in, như sau:

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

Bạn có thể nghĩ về mã Python xác định một phép tính liên kết tương tự như cách bạn nghĩ về mã Python xây dựng biểu đồ TensorFlow trong ngữ cảnh không háo hức (nếu bạn không quen với các cách sử dụng không háo hức của TensorFlow, hãy nghĩ về Mã Python xác định một biểu đồ của các hoạt động sẽ được thực thi sau này, nhưng không thực sự chạy chúng một cách nhanh chóng). Mã xây dựng đồ thị không háo hức trong TensorFlow là Python, nhưng biểu đồ TensorFlow do mã này xây dựng độc lập với nền tảng và có thể tuần tự hóa.

Tương tự như vậy, các phép tính TFF được định nghĩa bằng Python, nhưng các câu lệnh Python trong phần thân của chúng, chẳng hạn như tff.federated_mean trong ví dụ chúng ta vừa hiển thị, được biên dịch thành một biểu diễn tuần tự hóa độc lập và di động dưới mui xe.

Là một nhà phát triển, bạn không cần phải quan tâm đến các chi tiết của bản trình bày này, vì bạn sẽ không bao giờ cần trực tiếp làm việc với nó, nhưng bạn nên biết về sự tồn tại của nó, thực tế là các tính toán TFF về cơ bản là không cần thiết, và không thể nắm bắt trạng thái Python tùy ý. Mã Python chứa trong phần thân của tính toán TFF được thực thi tại thời điểm định nghĩa, khi phần thân của hàm Python được trang trí bằng tff.federated_computation được truy tìm trước khi được tuần tự hóa. Nó không được thực hiện lại vào thời điểm gọi (ngoại trừ khi hàm là đa hình; vui lòng tham khảo các trang tài liệu để biết thêm chi tiết).

Bạn có thể thắc mắc tại sao chúng tôi lại chọn giới thiệu một biểu diễn nội bộ chuyên dụng không phải Python. Một lý do là cuối cùng, các tính toán TFF nhằm mục đích có thể triển khai cho môi trường vật lý thực và được lưu trữ trên thiết bị di động hoặc thiết bị nhúng, nơi Python có thể không khả dụng.

Một lý do khác là các tính toán TFF thể hiện hành vi toàn cầu của các hệ thống phân tán, trái ngược với các chương trình Python thể hiện hành vi cục bộ của từng người tham gia. Bạn có thể thấy rằng trong ví dụ đơn giản ở trên, với toán tử đặc biệt tff.federated_mean chấp nhận dữ liệu trên các thiết bị khách nhưng lưu kết quả trên máy chủ.

Toán tử tff.federated_mean không thể dễ dàng được mô hình hóa như một toán tử thông thường trong Python, vì nó không thực thi cục bộ - như đã lưu ý trước đó, nó đại diện cho một hệ thống phân tán điều phối hành vi của nhiều người tham gia hệ thống. Chúng tôi sẽ đề cập đến các toán tử như vậy như các toán tử liên hợp , để phân biệt chúng với các toán tử thông thường (cục bộ) trong Python.

Hệ thống kiểu TFF và tập hợp các hoạt động cơ bản được hỗ trợ trong ngôn ngữ của TFF, do đó, sai lệch đáng kể so với hệ thống trong Python, yêu cầu sử dụng một biểu diễn chuyên dụng.

Soạn các phép tính liên hợp

Như đã lưu ý ở trên, các phép tính liên hợp và các thành phần của chúng được hiểu rõ nhất là các mô hình của hệ thống phân tán và bạn có thể nghĩ việc soạn các phép tính liên hợp giống như việc soạn các hệ thống phân tán phức tạp hơn từ các hệ thống đơn giản hơn. Bạn có thể nghĩ toán tử tff.federated_mean như một loại tính toán liên hợp mẫu tích hợp với chữ ký kiểu ({T}@CLIENTS -> T@SERVER) (thực sự, giống như các phép tính bạn viết, toán tử này cũng có một phức hợp cấu trúc - dưới mui xe, chúng tôi chia nhỏ nó thành các toán tử đơn giản hơn).

Điều này cũng đúng với việc soạn các phép tính liên hợp. Phép tính get_average_temperature có thể được gọi trong phần thân của một hàm Python khác được trang trí bằng tff.federated_computation - làm như vậy sẽ khiến nó được nhúng vào phần thân của cha mẹ, giống như cách mà tff.federated_mean đã được nhúng vào phần thân của chính nó trước đó.

Một hạn chế quan trọng cần lưu ý là phần thân của các hàm Python được trang trí bằng tff.federated_computation chỉ được bao gồm các toán tử được liên kết, tức là chúng không thể chứa trực tiếp các hoạt động TensorFlow. Ví dụ: bạn không thể trực tiếp sử dụng giao diện tf.nest để thêm một cặp giá trị được liên kết. Mã TensorFlow phải được giới hạn trong các khối mã được trang trí bằng tff.tf_computation thảo luận trong phần sau. Chỉ khi được bọc theo cách này, mã TensorFlow được bọc mới có thể được gọi trong phần thân của tff.federated_computation .

Lý do cho sự tách biệt này là do kỹ thuật (rất khó để lừa các nhà khai thác như tf.add làm việc với các khối không căng) cũng như kiến ​​trúc. Ngôn ngữ của tính toán liên hợp (tức là logic được xây dựng từ các phần thân được tuần tự hóa của các hàm Python được trang trí bằng tff.federated_computation ) được thiết kế để hoạt động như một ngôn ngữ keo độc lập với nền tảng. Ngôn ngữ keo này hiện được sử dụng để xây dựng các hệ thống phân tán từ các phần nhúng của mã TensorFlow (giới hạn trong các khối tff.tf_computation ). Trong thời gian dài, chúng tôi dự đoán cần phải nhúng các phần của logic khác, không phải TensorFlow, chẳng hạn như các truy vấn cơ sở dữ liệu quan hệ có thể đại diện cho các đường ống đầu vào, tất cả được kết nối với nhau bằng cùng một ngôn ngữ keo (các khối tff.federated_computation ).

Logic TensorFlow

Khai báo tính toán TensorFlow

TFF được thiết kế để sử dụng với TensorFlow. Do đó, phần lớn mã bạn sẽ viết trong TFF có thể là mã TensorFlow thông thường (tức là thực thi cục bộ). Để sử dụng mã như vậy với TFF, như đã nói ở trên, nó chỉ cần được trang trí bằng tff.tf_computation .

Ví dụ, đây là cách chúng ta có thể triển khai một hàm nhận một số và thêm 0.5 vào nó.

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

Một lần nữa, nhìn vào điều này, bạn có thể tự hỏi tại sao chúng ta nên định nghĩa một trình trang trí khác tff.tf_computation thay vì chỉ sử dụng một cơ chế hiện có như tf.function . tf.function . Không giống như trong phần trước, ở đây chúng ta đang xử lý một khối mã TensorFlow thông thường.

Có một vài lý do giải thích cho điều này, cách xử lý đầy đủ vượt ra ngoài phạm vi của hướng dẫn này, nhưng đáng để đặt tên cho nguyên nhân chính:

  • Để nhúng các khối xây dựng có thể tái sử dụng được triển khai bằng mã TensorFlow trong phần thân của các phép tính liên hợp, chúng cần đáp ứng các thuộc tính nhất định - chẳng hạn như được theo dõi và tuần tự hóa tại thời điểm xác định, có chữ ký kiểu, v.v. Điều này thường yêu cầu một số hình thức trang trí.

Nói chung, chúng tôi khuyên bạn nên sử dụng các cơ chế gốc của TensorFlow để tạo thành phần, chẳng hạn như tf.function , bất cứ khi nào có thể, vì cách thức chính xác mà trình trang trí của TFF tương tác với các chức năng mong muốn có thể được phát triển.

Bây giờ, quay lại đoạn mã ví dụ ở trên, add_half tính toán mà chúng ta vừa xác định có thể được TFF xử lý giống như bất kỳ phép tính TFF nào khác. Đặc biệt, nó có một chữ ký kiểu TFF.

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

Lưu ý rằng chữ ký loại này không có vị trí. Tính toán TensorFlow không thể sử dụng hoặc trả về các kiểu liên kết.

Bây giờ bạn cũng có thể sử dụng add_half như một khối xây dựng trong các tính toán khác. Ví dụ: đây là cách bạn có thể sử dụng toán tử tff.federated_map để áp dụng add_half pointwise cho tất cả các thành phần thành viên của float liên kết trên các thiết bị khách.

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

Thực thi tính toán TensorFlow

Việc thực thi các phép tính được xác định với tff.tf_computation tuân theo các quy tắc tương tự như những quy tắc chúng tôi đã mô tả cho tff.federated_computation . Chúng có thể được gọi dưới dạng các bảng gọi thông thường trong Python, như sau.

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

Một lần nữa, cần lưu ý rằng việc gọi add_half_on_clients tính toán theo cách này mô phỏng một quá trình phân tán. Dữ liệu được sử dụng trên máy khách và trả lại trên máy khách. Thật vậy, tính toán này có mỗi máy khách thực hiện một hành động cục bộ. Không có tff.SERVER được đề cập rõ ràng trong hệ thống này (ngay cả khi trong thực tế, việc sắp xếp xử lý như vậy có thể liên quan đến một). Hãy nghĩ về một phép tính được định nghĩa theo cách này về mặt khái niệm tương tự như giai đoạn Map trong MapReduce .

Ngoài ra, hãy nhớ rằng những gì chúng tôi đã nói trong phần trước về các phép tính TFF được tuần tự hóa tại thời điểm định nghĩa vẫn đúng đối với mã tff.tf_computation - phần thân Python của add_half_on_clients được truy tìm một lần tại thời điểm định nghĩa. Trong các lần gọi tiếp theo, TFF sử dụng biểu diễn tuần tự của nó.

Sự khác biệt duy nhất giữa các phương thức Python được trang trí bằng tff.federated_computation và những phương thức được trang trí bằng tff.tf_computation là cái sau được tuần tự hóa dưới dạng đồ thị TensorFlow (trong khi cái trước không được phép chứa mã TensorFlow được nhúng trực tiếp vào chúng).

Bên dưới, mỗi phương thức được trang trí bằng tff.tf_computation tạm thời vô hiệu hóa việc thực thi háo hức để cho phép cấu trúc của tính toán được nắm bắt. Trong khi quá trình thực thi háo hức bị vô hiệu hóa cục bộ, bạn có thể sử dụng các cấu trúc TensorFlow, AutoGraph, TensorFlow 2.0 háo hức, v.v., miễn là bạn viết logic tính toán của mình theo cách sao cho nó có thể được tuần tự hóa một cách chính xác.

Ví dụ: mã sau sẽ không thành công:

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.

Ở trên không thành công vì constant_10 đã được xây dựng bên ngoài của đồ thị mà tff.tf_computation xây dựng nội bộ trong cơ thể của add_ten trong quá trình tuần tự.

Mặt khác, việc gọi các hàm python sửa đổi biểu đồ hiện tại khi được gọi bên trong tff.tf_computation là tốt:

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

Lưu ý rằng các cơ chế tuần tự hóa trong TensorFlow đang phát triển và chúng tôi mong đợi các chi tiết về cách TFF tuần tự hóa các phép tính cũng sẽ phát triển.

Làm việc vớitf.data.Dataset s

Như đã lưu ý trước đó, một tính năng độc đáo của tff.tf_computation s là chúng cho phép bạn làm việc vớitf.data.Dataset được định nghĩa một cách trừu tượng dưới dạng các tham số chính thức bởi mã của bạn. Các tham số được biểu diễn trong TensorFlow dưới dạng tập dữ liệu cần được khai báo bằng cách sử dụng tff.SequenceType tạo tff.SequenceType .

For example, the type specification tff.SequenceType(tf.float32) defines an abstract sequence of float elements in TFF. Sequences can contain either tensors, or complex nested structures (we'll see examples of those later). The concise representation of a sequence of T -typed items is 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 liketf.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 ordinarytf.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 atf.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 .