![]() |
![]() |
![]() |
![]() |
本教學課程是分為兩個部分的系列教學課程的第一部分,示範如何在 TensorFlow Federated (TFF) 中使用Federated Core (FC) (一組較低階的介面,是我們實作Federated Learning (FL) 層的基礎) 實作自訂類型的 Federated Algorithm。
第一部分更偏重概念;我們將介紹 TFF 中使用的一些重要概念和程式設計抽象概念,並透過分散式溫度感應器陣列的非常簡單範例示範其用法。在本系列的第二部分中,我們將使用此處介紹的機制來實作簡單版本的 Federated Training 和評估演算法。作為後續步驟,我們建議您研究 Federated Averaging 的實作,網址為 tff.learning
。
在本系列結束時,您應該能夠認識到 Federated Core 的應用不一定僅限於學習。我們提供的程式設計抽象概念非常通用,可用於實作分析和其他自訂類型的分散式資料運算等。
雖然本教學課程旨在獨立運作,但我們仍建議您先閱讀關於圖片分類和文字產生的教學課程,以更深入淺出地瞭解 TensorFlow Federated 架構和 Federated Learning API (tff.learning
),因為這將有助於您將我們在此處描述的概念放入背景脈絡中理解。
預期用途
簡而言之,Federated Core (FC) 是一種開發環境,可讓您簡潔地表達將 TensorFlow 程式碼與分散式通訊運算子結合的程式邏輯,例如 Federated Averaging 中使用的運算子 - 在系統中一組用戶端裝置上計算分散式總和、平均值和其他類型的分散式彙總、將模型和參數廣播到這些裝置等。
您可能知道 tf.contrib.distribute
,此時自然會問一個問題:此架構有何不同之處?畢竟,這兩個架構都嘗試使 TensorFlow 運算分散式化。
一種思考方式是,tf.contrib.distribute
的既定目標是允許使用者使用現有模型和訓練程式碼,並進行最少的變更即可啟用分散式訓練,並且重點主要放在如何利用分散式基礎架構來提高現有訓練程式碼的效率,而 TFF Federated Core 的目標是讓研究人員和從業人員能夠明確控制他們將在系統中使用的特定分散式通訊模式。FC 的重點是提供一種彈性且可擴充的語言來表達分散式資料流程演算法,而不是一組已實作的分散式訓練功能。
TFF 的 FC API 的主要目標受眾之一是可能想要試驗新的 Federated Learning 演算法,並評估影響分散式系統中資料流程協調方式的細微設計選擇後果的研究人員和從業人員,但又不會陷入系統實作細節的困境。FC API 旨在達成的抽象層級大致對應於人們可能用來描述研究出版物中 Federated Learning 演算法機制的虛擬碼 - 系統中存在哪些資料以及如何轉換資料,但不會降至個別點對點網路訊息交換的層級。
TFF 整體而言,目標情境是資料是分散式的,而且必須保持分散式,例如,基於隱私原因,以及在集中位置收集所有資料可能不是可行的選項。與所有資料都可以在資料中心的集中位置累積的情境相比,這對需要更高程度明確控制的機器學習演算法的實作具有影響。
開始之前
在深入瞭解程式碼之前,請嘗試執行以下「Hello World」範例,以確保您的環境設定正確。如果無法運作,請參閱安裝指南以取得說明。
pip install --quiet --upgrade tensorflow-federated
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!'
Federated 資料
TFF 的區分特徵之一是,它可讓您簡潔地表達以 TensorFlow 為基礎對Federated 資料進行的運算。在本教學課程中,我們將使用術語 Federated 資料 來指分散式系統中一組裝置上託管的資料項目集合。例如,在行動裝置上執行的應用程式可能會收集資料並將其儲存在本機,而不會上傳到集中位置。或者,分散式感應器陣列可能會在其位置收集和儲存溫度讀數。
在 TFF 中,如以上範例中的 Federated 資料被視為一級公民,即它們可以作為函式的參數和結果出現,並且它們具有類型。為了加強此概念,我們將 Federated 資料集稱為 Federated 值,或稱為 Federated 類型的值。
需要理解的重點是,我們將跨所有裝置的整個資料項目集合 (例如,分散式陣列中所有感應器的整個溫度讀數集合) 建模為單一 Federated 值。
例如,以下是如何在 TFF 中定義由一組用戶端裝置託管的 Federated float 類型。跨分散式感應器陣列具體化的溫度讀數集合可以建模為此 Federated 類型的值。
federated_float_on_clients = tff.FederatedType(np.float32, tff.CLIENTS)
更廣泛地說,TFF 中的 Federated 類型是透過指定其成員組成部分的類型 T
(駐留在個別裝置上的資料項目) 和託管此類型 Federated 值的裝置群組 G
(加上我們稍後會提及的第三個選用資訊位元) 來定義的。我們將託管 Federated 值的裝置群組 G
稱為值的位置。因此,tff.CLIENTS
是位置的範例。
str(federated_float_on_clients.member)
'float32'
str(federated_float_on_clients.placement)
'CLIENTS'
具有成員組成部分 T
和位置 G
的 Federated 類型可以簡潔地表示為 {T}@G
,如下所示。
str(federated_float_on_clients)
'{float32}@CLIENTS'
此簡潔表示法中的大括號 {}
用於提醒成員組成部分 (不同裝置上的資料項目) 可能會有所不同,正如您對溫度感應器讀數所預期的那樣,因此用戶端作為一個群組共同託管 多重集 的 T
類型項目,這些項目共同構成 Federated 值。
務必注意,Federated 值的成員組成部分通常對程式設計人員是不透明的,即不應將 Federated 值視為簡單的 dict
,其索引鍵是系統中裝置的識別碼 - 這些值僅應由 Federated 運算子 集體轉換,這些運算子抽象地表示各種分散式通訊協定 (例如彙總)。如果這聽起來太抽象,請別擔心 - 我們稍後會回到這一點,並透過具體範例來說明。
TFF 中的 Federated 類型有兩種:Federated 值的成員組成部分可能不同的類型 (如剛才所見),以及已知所有成員組成部分都相等的類型。這由 tff.FederatedType
建構函式中的第三個選用 all_equal
參數控制 (預設為 False
)。
federated_float_on_clients.all_equal
False
位置為 G
的 Federated 類型,其中所有 T
類型成員組成部分都已知相等,可以簡潔地表示為 T@G
(與 {T}@G
相反,也就是說,省略大括號以反映成員組成部分的多重集由單一項目組成的事實)。
str(tff.FederatedType(np.float32, tff.CLIENTS, all_equal=True))
'float32@CLIENTS'
在實際情境中可能出現的此類 Federated 值的一個範例是超參數 (例如學習率、剪輯範數等),該超參數已由伺服器廣播到參與 Federated Training 的一組裝置。
另一個範例是一組在伺服器上預先訓練的機器學習模型參數,然後將其廣播到一組用戶端裝置,以便在每個使用者端進行個人化。
例如,假設我們有一對 float32
參數 a
和 b
用於簡單的一維線性迴歸模型。我們可以建構此類模型 (非 Federated) 類型,以在 TFF 中使用,如下所示。列印的類型字串中的角括號 <>
是具名或未命名元組的簡潔 TFF 表示法。
simple_regression_model_type = (
tff.StructType([('a', np.float32), ('b', np.float32)]))
str(simple_regression_model_type)
'<a=float32,b=float32>'
請注意,我們在上面僅指定 dtype
。也支援非純量類型。在上述程式碼中,np.float32
是更通用的 tff.TensorType(np.float32, [])
的捷徑表示法。
當此模型廣播到用戶端時,產生的 Federated 值的類型可以表示如下所示。
str(tff.FederatedType(
simple_regression_model_type, tff.CLIENTS, all_equal=True))
'<a=float32,b=float32>@CLIENTS'
根據上述 Federated float 的對稱性,我們將此類類型稱為 Federated 元組。更廣泛地說,我們通常會使用術語 Federated XYZ 來指成員組成部分類似 XYZ 的 Federated 值。因此,我們將討論諸如 Federated 元組、Federated 序列、Federated 模型等。
現在,回到 float32@CLIENTS
- 雖然它似乎在多個裝置上重複,但它實際上是單一 float32
,因為所有成員都相同。一般而言,您可以將任何 all-equal Federated 類型 (即 T@G
形式的類型) 視為與非 Federated 類型 T
同構,因為在這兩種情況下,實際上都只有單一 (儘管可能是重複的) T
類型項目。
鑑於 T
和 T@G
之間的同構,您可能會想知道後者類型可能有哪些用途 (如果有的話)。請繼續閱讀。
位置
設計總覽
在前一節中,我們介紹了位置的概念 - 可能共同託管 Federated 值的系統參與者群組,並且我們示範了如何使用 tff.CLIENTS
作為位置的範例規格。
為了說明為什麼位置的概念如此基本,以至於我們需要將其納入 TFF 類型系統中,請回想一下我們在本教學課程開頭提及的關於 TFF 的一些預期用途。
雖然在本教學課程中,您只會看到 TFF 程式碼在本機模擬環境中執行,但我們的目標是讓 TFF 能夠編寫您可以部署以在分散式系統中的實體裝置群組上執行的程式碼,其中可能包括執行 Android 的行動或嵌入式裝置。這些裝置中的每一個都將收到一組單獨的指令以在本機執行,具體取決於它在系統中扮演的角色 (終端使用者裝置、集中式協調器、多層架構中的中間層等)。能夠推理出哪些裝置子集執行哪些程式碼,以及資料的不同部分可能在何處實際具體化非常重要。
當處理例如行動裝置上的應用程式資料時,這一點尤其重要。由於資料是私密的且可能很敏感,因此我們需要能夠靜態驗證此資料永遠不會離開裝置 (並證明有關資料如何處理的事實)。位置規格是旨在支援此目的的機制之一。
TFF 設計為以資料為中心的程式設計環境,因此,與某些現有的專注於運算以及這些運算可能執行在哪裡的架構不同,TFF 專注於資料、資料實際具體化的位置以及資料如何被轉換。因此,位置被建模為 TFF 中資料的屬性,而不是資料運算的屬性。實際上,正如您即將在下一節中看到的那樣,某些 TFF 運算跨越多個位置,並且可以說是在「網路中」執行,而不是由單一機器或機器群組執行。
將特定值的類型表示為 T@G
或 {T}@G
(而不是僅僅 T
) 使資料位置決策變得明確,並且結合對以 TFF 撰寫的程式進行靜態分析,它可以作為為敏感裝置上資料提供正式隱私保證的基礎。
但是,此時需要注意的重要事項是,雖然我們鼓勵 TFF 使用者明確說明託管資料的參與裝置群組 (位置),但程式設計人員永遠不會處理原始資料或個別參與者的身分。
在 TFF 程式碼主體中,依設計,無法列舉構成 tff.CLIENTS
所代表群組的裝置,或探測群組中是否存在特定裝置。在 Federated Core API、底層架構抽象概念集或我們提供的支援模擬的核心執行階段基礎架構中,任何地方都沒有裝置或用戶端身分的概念。您編寫的所有運算邏輯都將表示為對整個用戶端群組的運算。
在此回想一下我們之前提到的關於 Federated 類型的值與 Python dict
不同之處,即人們不能簡單地列舉其成員組成部分。將您的 TFF 程式邏輯操作的值視為與位置 (群組) 相關聯,而不是與個別參與者相關聯。
位置也設計為 TFF 中的一級公民,並且可以作為 placement
類型 (在 API 中由 tff.PlacementType
表示) 的參數和結果出現。未來,我們計劃提供各種運算子來轉換或組合位置,但這超出了本教學課程的範圍。目前,將 placement
視為 TFF 中的不透明基本內建類型就足夠了,類似於 int
和 bool
是 Python 中的不透明內建類型,而 tff.CLIENTS
是此類型的常數文字,與 1
是 int
類型的常數文字類似。
指定位置
TFF 提供兩個基本位置文字,tff.CLIENTS
和 tff.SERVER
,以便輕鬆表達各種實際情境,這些情境自然地建模為用戶端-伺服器架構,其中多個用戶端裝置 (手機、嵌入式裝置、分散式資料庫、感應器等) 由單一集中式伺服器協調器協調。TFF 也旨在支援自訂位置、多個用戶端群組、多層和其他更通用的分散式架構,但討論它們超出了本教學課程的範圍。
TFF 不規定 tff.CLIENTS
或 tff.SERVER
實際代表什麼。
特別是,tff.SERVER
可能是單一實體裝置 (單例群組的成員),但也可能是一組容錯叢集中的副本,這些副本執行狀態機複製 - 我們不做任何特殊的架構假設。相反,我們使用前一節中提及的 all_equal
位元來表達我們通常只處理伺服器上的單一資料項目。
同樣地,在某些應用程式中,tff.CLIENTS
可能代表系統中的所有用戶端 - 在 Federated Learning 的背景脈絡中,我們有時將其稱為母體,但在例如 Federated Averaging 的生產環境實作中,它可能代表群組 - 為參與特定訓練回合而選取的一組用戶端子集。當部署在其中出現位置的運算以執行時 (或只是像 Python 函式一樣在模擬環境中叫用,如本教學課程所示),抽象定義的位置會被賦予具體意義。在我們的本機模擬中,用戶端群組由作為輸入提供的 Federated 資料決定。
Federated 運算
宣告 Federated 運算
TFF 設計為強類型函數式程式設計環境,支援模組化開發。
TFF 中的基本組合單位是 Federated 運算 - 一段邏輯,可以接受 Federated 值作為輸入,並傳回 Federated 值作為輸出。以下是如何定義運算的範例,該運算會計算我們先前範例中感應器陣列回報的平均溫度。
@tff.federated_computation(tff.FederatedType(np.float32, tff.CLIENTS))
def get_average_temperature(sensor_readings):
return tff.federated_mean(sensor_readings)
查看上面的程式碼,此時您可能會問 - 是否已經有裝飾器建構來定義可組合單位,例如 TensorFlow 中的 tf.function
,如果是這樣,為什麼還要引入另一個,以及它有何不同?
簡短的答案是,由 tff.federated_computation
包裝函式產生的程式碼既不是 TensorFlow,也不是 Python - 它是在內部平台獨立膠水語言中分散式系統的規格。此時,這無疑會聽起來很神秘,但請記住將 Federated 運算直覺地解釋為分散式系統的抽象規格。我們稍後會解釋。
首先,讓我們稍微研究一下定義。TFF 運算通常建模為函式 - 帶有或不帶參數,但具有明確定義的類型簽章。您可以透過查詢運算的 type_signature
屬性來列印運算的類型簽章,如下所示。
str(get_average_temperature.type_signature)
'({float32}@CLIENTS -> float32@SERVER)'
類型簽章告訴我們,運算接受用戶端裝置上不同感應器讀數的集合,並在伺服器上傳回單一平均值。
在我們繼續之前,讓我們花一點時間思考一下 - 此運算的輸入和輸出位於不同的位置 (在 CLIENTS
與 SERVER
)。回想一下我們在前一節中關於位置的內容,即 TFF 運算可能跨越多個位置,並且在網路中執行,以及我們剛才說的關於 Federated 運算表示分散式系統抽象規格的內容。我們剛才定義了一個此類運算 - 一個簡單的分散式系統,其中資料在用戶端裝置上使用,而彙總結果則在伺服器上產生。
在許多實際情境中,表示最上層工作的運算往往會接受其輸入並在伺服器上報告其輸出 - 這反映了運算可能會由伺服器發起和終止的查詢觸發的想法。
但是,FC API 沒有強加此假設,而且我們在內部使用的許多建構區塊 (包括您可能在 API 中找到的許多 tff.federated_...
運算子) 都具有具有不同位置的輸入和輸出,因此一般而言,您不應將 Federated 運算視為在伺服器上執行或由伺服器執行的事物。伺服器只是 Federated 運算中的一種參與者。在思考此類運算的機制時,最好始終預設為全域網路範圍的視角,而不是單一集中式協調器的視角。
一般而言,函數式類型簽章簡潔地表示為 (T -> U)
,分別表示輸入和輸出的類型 T
和 U
。形式參數的類型 (在本例中為 sensor_readings
) 指定為裝飾器的引數。您不需要指定結果的類型 - 它會自動判斷。
雖然 TFF 確實提供有限形式的多型,但強烈建議程式設計人員明確說明他們使用的資料類型,因為這可以更輕鬆地理解、偵錯和正式驗證程式碼的屬性。在某些情況下,明確指定類型是必需的 (例如,多型運算目前無法直接執行)。
執行 Federated 運算
為了支援開發和偵錯,TFF 允許您直接叫用以此方式定義的運算作為 Python 函式,如下所示。當運算預期 Federated 類型的值,且 all_equal
位元設定為 False
時,您可以將其作為純 list
在 Python 中饋送,而對於 all_equal
位元設定為 True
的 Federated 類型,您可以直接饋送 (單一) 成員組成部分。這也是結果回報給您的方式。
get_average_temperature([68.5, 70.3, 69.8])
69.53334
在模擬模式下執行像這樣的計算時,您會作為具有系統全局視角的外部觀察者,能夠在網路中的任何位置提供輸入和消耗輸出,而這裡的情況確實如此 - 您在輸入端提供了客戶端值,並消耗了伺服器結果。
現在,讓我們回到稍早關於 tff.federated_computation
裝飾器發出膠水語言程式碼的註解。儘管 TFF 運算的邏輯可以表示為 Python 中的普通函數(您只需要使用 tff.federated_computation
來裝飾它們,就像我們上面所做的那樣),並且您可以像在本筆記本中對待任何其他 Python 函數一樣,使用 Python 引數直接調用它們,但在幕後,正如我們稍早所指出的,TFF 運算實際上並非 Python。
我們這樣說的意思是,當 Python 直譯器遇到使用 tff.federated_computation
裝飾的函數時,它會在定義時追蹤此函數主體中的語句一次,然後建構運算邏輯的 序列化表示,以供未來使用 - 無論是用於執行,還是作為子組件併入另一個運算中。
您可以透過新增 print 語句來驗證這一點,如下所示
@tff.federated_computation(tff.FederatedType(np.float32, tff.CLIENTS))
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 "Value".
您可以將定義聯邦運算的 Python 程式碼,視為類似於您在非 Eager Context 中建構 TensorFlow 圖形的 Python 程式碼(如果您不熟悉 TensorFlow 的非 Eager 用法,請將您的 Python 程式碼視為定義要稍後執行的運算圖形,但實際上並未即時執行它們)。TensorFlow 中非 Eager 圖形建構程式碼是 Python,但此程式碼建構的 TensorFlow 圖形是平台獨立且可序列化的。
同樣地,TFF 運算是在 Python 中定義的,但它們主體中的 Python 語句,例如我們剛展示的範例中的 tff.federated_mean
,會在底層編譯成可移植且平台獨立的可序列化表示。
作為開發人員,您不需要擔心此表示的細節,因為您永遠不需要直接使用它,但您應該意識到它的存在、TFF 運算從根本上是非 Eager 的事實,並且無法捕獲任意 Python 狀態。TFF 運算主體中包含的 Python 程式碼會在定義時執行,當使用 tff.federated_computation
裝飾的 Python 函數主體在序列化之前被追蹤時。在調用時不會再次追蹤它(除非函數是多型的;請參閱文件頁面以了解詳細資訊)。
您可能會想知道為什麼我們選擇引入專用的內部非 Python 表示。一個原因是,最終,TFF 運算旨在部署到真實的物理環境中,並託管在行動裝置或嵌入式裝置上,而這些裝置可能無法使用 Python。
另一個原因是,TFF 運算表達了分散式系統的全局行為,而不是 Python 程式表達個別參與者的本地行為。您可以在上面的簡單範例中看到這一點,特殊的運算子 tff.federated_mean
接受客戶端裝置上的資料,但將結果存放在伺服器上。
運算子 tff.federated_mean
無法輕易地在 Python 中建模為普通運算子,因為它不是在本地執行的 - 正如稍早所述,它代表一個分散式系統,協調多個系統參與者的行為。我們將把這類運算子稱為聯邦運算子,以區分它們與 Python 中的普通(本地)運算子。
因此,TFF 類型系統和 TFF 語言中支援的基本運算集合,與 Python 中的那些顯著不同,因此需要使用專用的表示。
組合聯邦運算
如上所述,聯邦運算及其組成部分最好理解為分散式系統的模型,您可以將組合聯邦運算視為從較簡單的系統組合更複雜的分散式系統。您可以將 tff.federated_mean
運算子視為一種內建的範本聯邦運算,具有類型簽章 ({T}@CLIENTS -> T@SERVER)
(實際上,就像您編寫的運算一樣,此運算子也具有複雜的結構 - 在底層,我們將其分解為更簡單的運算子)。
組合聯邦運算也是如此。get_average_temperature
運算可以在另一個使用 tff.federated_computation
裝飾的 Python 函數主體中調用 - 這樣做會導致它嵌入到父函數的主體中,很像 tff.federated_mean
先前嵌入到其自身主體中的方式。
需要注意的一個重要限制是,使用 tff.federated_computation
裝飾的 Python 函數主體只能包含聯邦運算子,也就是說,它們不能直接包含 TensorFlow 運算。例如,您不能直接使用 tf.nest
介面來新增一對聯邦值。TensorFlow 程式碼必須限制在以 tff.tensorflow.computation
裝飾的程式碼區塊中,這將在以下章節中討論。只有以這種方式包裝後,包裝的 TensorFlow 程式碼才能在使用 tff.federated_computation
的主體中調用。
這種分離的原因是技術性的(很難欺騙像 tf.add
這樣的運算子來處理非張量),以及架構性的。聯邦運算的語言(即,從使用 tff.federated_computation
裝飾的 Python 函數的序列化主體建構的邏輯)旨在作為平台獨立的膠水語言。這種膠水語言目前用於從 TensorFlow 程式碼的嵌入部分(限制在 tff.tensorflow.computation
區塊中)建構分散式系統。在未來,我們預計需要嵌入其他非 TensorFlow 邏輯的部分,例如可能代表輸入管線的關係資料庫查詢,所有這些都使用相同的膠水語言(tff.federated_computation
區塊)連接在一起。
TensorFlow 邏輯
宣告 TensorFlow 運算
TFF 設計用於 TensorFlow。因此,您在 TFF 中編寫的大部分程式碼很可能是普通的(即,本地執行的)TensorFlow 程式碼。為了將此類程式碼與 TFF 一起使用,如上所述,只需使用 tff.tensorflow.computation
裝飾即可。
例如,以下是如何實作一個函數,該函數接收一個數字並將 0.5
加到它上面。
@tff.tensorflow.computation(np.float32)
def add_half(x):
return tf.add(x, 0.5)
再次查看此處,您可能會想知道為什麼我們應該定義另一個裝飾器 tff.tensorflow.computation
,而不是簡單地使用現有的機制,例如 tf.function
。與前一節不同,這裡我們正在處理普通的 TensorFlow 程式碼區塊。
這有幾個原因,其完整處理超出了本教學課程的範圍,但值得一提的是主要原因
- 為了將使用 TensorFlow 程式碼實作的可重複使用建構區塊嵌入到聯邦運算的主體中,它們需要滿足某些屬性 - 例如在定義時被追蹤和序列化、具有類型簽章等等。這通常需要某種形式的裝飾器。
總體而言,我們建議盡可能使用 TensorFlow 的原生組合機制,例如 tf.function
,因為 TFF 的裝飾器與 Eager 函數互動的確切方式預計會不斷發展。
現在,回到上面的範例程式碼片段,我們剛定義的 add_half
運算可以被 TFF 視為任何其他 TFF 運算。特別是,它具有 TFF 類型簽章。
str(add_half.type_signature)
'(float32 -> float32)'
請注意,此類型簽章沒有位置。TensorFlow 運算無法消耗或傳回聯邦類型。
您現在也可以使用 add_half
作為其他運算中的建構區塊。例如,以下是如何使用 tff.federated_map
運算子,將 add_half
逐點應用於客戶端裝置上聯邦浮點數的所有成員組成部分。
@tff.federated_computation(tff.FederatedType(np.float32, tff.CLIENTS))
def add_half_on_clients(x):
return tff.federated_map(add_half, x)
str(add_half_on_clients.type_signature)
'({float32}@CLIENTS -> {float32}@CLIENTS)'
執行 TensorFlow 運算
使用 tff.tensorflow.computation
定義的運算的執行,遵循我們針對 tff.federated_computation
所描述的相同規則。它們可以在 Python 中作為普通的可調用物件調用,如下所示。
add_half_on_clients([1.0, 3.0, 2.0])
[<tf.Tensor: shape=(), dtype=float32, numpy=1.5>, <tf.Tensor: shape=(), dtype=float32, numpy=3.5>, <tf.Tensor: shape=(), dtype=float32, numpy=2.5>]
再次強調,值得注意的是,以這種方式調用 add_half_on_clients
運算會模擬分散式程序。資料在客戶端上消耗,並在客戶端上傳回。實際上,此運算讓每個客戶端執行本地動作。此系統中沒有明確提及 tff.SERVER
(即使在實務上,協調此類處理可能需要一個)。將以這種方式定義的運算視為概念上類似於 MapReduce
中的 Map
階段。
此外,請記住,我們在前一節中關於 TFF 運算在定義時被序列化的說法,對於 tff.tensorflow.computation
程式碼也仍然適用 - add_half_on_clients
的 Python 主體在定義時會被追蹤一次。在後續調用中,TFF 會使用其序列化表示。
使用 tff.federated_computation
裝飾的 Python 方法與使用 tff.tensorflow.computation
裝飾的方法之間的唯一區別在於,後者被序列化為 TensorFlow 圖形(而前者不允許在其主體中直接嵌入 TensorFlow 程式碼)。
在底層,每個使用 tff.tensorflow.computation
裝飾的方法都會暫時停用 Eager 執行,以便捕獲運算的結構。雖然 Eager 執行在本地停用,但歡迎您使用 Eager TensorFlow、AutoGraph、TensorFlow 2.0 建構等,只要您以可以正確序列化的方式編寫運算的邏輯即可。
例如,以下程式碼將會失敗
try:
# Eager mode
constant_10 = tf.constant(10.)
@tff.tensorflow.computation(np.float32)
def add_ten(x):
return x + constant_10
except Exception as err:
print (err)
Attempting to capture an EagerTensor without building a function.
上面的程式碼失敗,是因為 constant_10
已經在 tff.tensorflow.computation
在 add_ten
的主體中內部建構的圖形之外建構。
另一方面,在 tff.tensorflow.computation
內部調用時,調用修改目前圖形的 Python 函數是可以的
def get_constant_10():
return tf.constant(10.)
@tff.tensorflow.computation(np.float32)
def add_ten(x):
return x + get_constant_10()
add_ten(5.0)
15.0
請注意,TensorFlow 中的序列化機制正在發展,我們也預期 TFF 序列化運算的細節也會不斷發展。
使用 tf.data.Dataset
如稍早所述,tff.tensorflow.computation
的一個獨特功能是,它們允許您使用 tf.data.Dataset
,這些 tf.data.Dataset
在您的程式碼中抽象地定義為形式參數。要以 TensorFlow 中的資料集表示的參數,需要使用 tff.SequenceType
建構函式宣告。
例如,類型規格 tff.SequenceType(np.float32)
在 TFF 中定義了浮點元素的抽象序列。序列可以包含張量,或複雜的巢狀結構(我們稍後會看到這些範例)。T
類型項目的序列的簡潔表示是 T*
。
float32_sequence = tff.SequenceType(np.float32)
str(float32_sequence)
'float32*'
假設在我們的溫度感測器範例中,每個感測器不僅保存一個溫度讀數,而是多個。以下是如何在 TensorFlow 中定義一個 TFF 運算,該運算使用 tf.data.Dataset.reduce
運算子,計算單個本地資料集中的溫度平均值。
@tff.tensorflow.computation(tff.SequenceType(np.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)'
在使用 tff.tensorflow.computation
裝飾的方法主體中,TFF 序列類型的形式參數僅僅表示為行為類似於 tf.data.Dataset
的物件,即支援相同的屬性和方法(它們目前未實作為該類型的子類別 - 隨著 TensorFlow 中對資料集的支援不斷發展,這可能會改變)。
您可以輕鬆地驗證這一點,如下所示。
@tff.tensorflow.computation(tff.SequenceType(np.int32))
def foo(x):
return x.reduce(np.int32(0), lambda x, y: x + y)
foo([1, 2, 3])
6
請記住,與普通的 tf.data.Dataset
不同,這些類資料集物件是佔位符。它們不包含任何元素,因為它們表示抽象序列類型參數,當在具體環境中使用時,將綁定到具體資料。目前對抽象定義的佔位符資料集的支援仍然有些有限,在 TFF 的早期,您可能會遇到某些限制,但在本教學課程中我們無需擔心它們(請參閱文件頁面以了解詳細資訊)。
當在模擬模式下本地執行接受序列的運算時,例如在本教學課程中,您可以將序列作為 Python 清單饋送,如下所示(以及以其他方式,例如,作為 Eager 模式下的 tf.data.Dataset
,但就目前而言,我們將保持簡單)。
get_local_temperature_average([68.5, 70.3, 69.8])
69.53333
與所有其他 TFF 類型一樣,像上面定義的序列可以使用 tff.StructType
建構函式來定義巢狀結構。例如,以下是如何宣告一個運算,該運算接受一對 A
、B
的序列,並傳回它們乘積的總和。我們在運算主體中包含追蹤語句,以便您可以看到 TFF 類型簽章如何轉換為資料集的 output_types
和 output_shapes
。
@tff.tensorflow.computation(tff.SequenceType(collections.OrderedDict([('A', np.int32), ('B', np.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
儘管在簡單的場景(如本教學課程中使用的場景)中可以使用,但對使用 tf.data.Datasets
作為形式參數的支援仍然有些有限且不斷發展。
整合在一起
現在,讓我們再次嘗試在聯邦設定中使用我們的 TensorFlow 運算。假設我們有一組感測器,每個感測器都有一系列本地溫度讀數。我們可以透過平均感測器的本地平均值來計算全局溫度平均值,如下所示。
@tff.federated_computation(
tff.FederatedType(tff.SequenceType(np.float32), tff.CLIENTS))
def get_global_temperature_average(sensor_readings):
return tff.federated_mean(
tff.federated_map(get_local_temperature_average, sensor_readings))
請注意,這不是所有客戶端的所有本地溫度讀數的簡單平均值,因為這需要根據不同客戶端本地維護的讀數數量來權衡來自不同客戶端的貢獻。我們將更新上述程式碼作為讀者的練習;tff.federated_mean
運算子接受權重作為可選的第二個引數(預期為聯邦浮點數)。
另請注意,get_global_temperature_average
的輸入現在變成聯邦浮點數序列。聯邦序列是我們通常在聯邦學習中表示裝置上資料的方式,序列元素通常表示資料批次(您很快就會看到這方面的範例)。
str(get_global_temperature_average.type_signature)
'({float32*}@CLIENTS -> float32@SERVER)'
以下是如何在 Python 中對資料樣本本地執行運算。請注意,我們提供輸入的方式現在是 list
的 list
。外部清單迭代 tff.CLIENTS
表示的群組中的裝置,而內部清單迭代每個裝置的本地序列中的元素。
get_global_temperature_average([[68.0, 70.0], [71.0], [68.0, 72.0, 70.0]])
70.0
本教學課程的第一部分到此結束... 我們鼓勵您繼續閱讀第二部分。