Python-联邦学习-全-
Python 联邦学习(全)
原文:
annas-archive.org/md5/59885cc35c2ca63f9f092895c4c3c9b9译者:飞龙
前言
联邦学习(FL)正在成为人工智能领域的一个颠覆性技术,因为它经常被说成,在联邦学习框架中,需要移动的是机器学习模型而不是数据本身,这样智能才能持续发展和增长。因此,人们将 FL 称为以模型为中心的方法,相对于传统的以数据为中心的方法,因此被认为是一项变革性技术。以模型为中心的方法的理念可以创建一个以智能为中心的平台,开创智慧驱动的世界。
通过采用 FL,你可以克服大数据人工智能长期面临的一些挑战,如数据隐私、训练成本和效率,以及最新智能的交付延迟。然而,FL 并不是一个通过盲目聚合机器学习模型就能解决大数据所有问题的魔法解决方案。我们需要非常仔细地设计分布式系统和学习机制,以同步所有分布式学习过程并一致地综合所有本地训练的机器学习模型。这样,我们可以创建一个可持续且具有弹性的 FL 系统,即使在规模化的实际操作中也能持续运行。
因此,这本书不仅超越了描述联邦学习概念和理论方面的内容,正如许多研究项目中通过模拟器或原型引入的,这些原型在大多数与该领域相关的文献中都有介绍。相反,你将通过查看简化联邦学习系统的代码来了解整个设计和实现原则,以验证框架的工作原理和结果。
在阅读完这本书之后,你将创建第一个基于联邦学习的应用程序,该程序可以在本地和云环境中各种设置中安装和测试。
这本书面向谁
这本书是为那些想要了解如何利用 FL 创建机器学习应用程序的机器学习工程师、数据科学家和 AI 爱好者而写的。为了开始阅读这本书,你需要具备基本的 Python 编程知识和机器学习概念。
这本书涵盖的内容
第一章,大数据和传统人工智能的挑战,主要关于指导你了解大数据系统和传统集中式机器学习方法的当前问题,以及联邦学习如何解决这些问题,例如数据隐私、偏差和孤岛、模型漂移和性能下降。这将为你深入了解联邦学习系统的设计和实现做好准备。
第二章, 什么是联邦学习?,继续介绍 FL,并帮助你通过当前分布式学习的趋势以及机器学习基础知识来理解 FL 的概念。你还将了解 FL 作为以模型为中心的机器学习方法在数据隐私和安全、效率以及可扩展性方面的优势。
第三章,联邦学习系统的工作原理,为你提供了一个坚实的理解,了解 FL 系统将如何工作以及 FL 系统内部分布式组件之间的交互。你将了解 FL 系统的基本架构,包括其流程、状态转换以及向 FL 系统连续运行的消息序列。
第四章, 使用 Python 实现联邦学习服务器,指导你学习 FL 服务器端系统的实现原理,包括数据库服务器和聚合器模块。本章还讨论了 FL 组件之间的通信、模型聚合以及如何管理分布式学习代理和本地及全局模型的状态。
第五章, 联邦学习客户端实现,描述了 FL 客户端功能以及可以由本地机器学习引擎和进程使用的库。核心客户端功能包括将分布式学习代理注册到 FL 平台、全局模型接收以及本地模型上传和共享。
第六章, 运行联邦学习系统并分析结果,将帮助你运行简化的 FL 框架,以了解 FL 系统的行为以及流程,以及联邦平均的最标准模型聚合方法。你还将能够通过分析两个示例测试用例的结果来验证运行 FL 框架的结果。
第七章, 模型聚合,是理解模型聚合的重要章节,这是 FL 的基础。你将了解不同的 FL 场景特征如何要求不同的聚合方法,并应该对如何实现这些算法有一个概念。
第八章, 介绍现有的联邦学习框架,解释了现有的 FL 项目和方法,如 PySyft、TFF、Flower、OpenFL 和 STADLE,以及它们的 API。有许多具有不同设计哲学的有用 FL 项目,你将了解这些框架的功能和差异。
第九章,联邦学习应用的关键用例案例研究,介绍了 FL 在不同行业中的主要用例。您将熟悉 FL 在医疗保健、金融行业、边缘计算、物联网以及大数据分布式学习等各个领域的应用,在这些领域,FL 显示出克服许多重要技术挑战的显著潜力。
第十章,未来趋势与发展,描述了由持续的研究和开发驱动的联邦学习(FL)技术的未来方向。您将了解到智能互联网和以智慧为中心的平台的新视角。因此,您将准备好迎接集体智慧的世界。
附录,探索内部库,概述了内部库,包括用于实现 FL 系统的枚举类、通信协议、数据结构处理器以及辅助和支持函数。
要充分利用本书
您需要在计算机上安装 Python 3.7+版本。为了轻松运行书中的代码示例,建议在 macOS 或 Linux 上安装 Anaconda 以创建虚拟环境。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Python 3.7+ | macOS 或 Linux |
| Anaconda 环境 | |
| GitHub |
您可以在任何云环境中安装 GitHub 仓库中提供的服务器端代码,例如亚马逊网络服务(AWS)或谷歌云平台(GCP),并设置适当的安全设置以建立分布式学习环境。
如果您正在使用本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub(github.com/PacktPublishing/Federated-Learning-with-Python)下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他丰富的图书和视频的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
请检查github.com/tie-set/simple-fl仓库以获取最新代码。
注意
您可以使用代码文件用于个人或教育目的。请注意,我们不会支持商业部署,并且不对使用代码引起的任何错误、问题或损害负责。
下载彩色图像
我们还提供了一个包含本书中使用的截图和图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/qh1su。
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“服务器代码为 FL 进程导入了StateManager和Aggregator。”
代码块设置如下:
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel
任何命令行输入或输出都应如下编写:
fx envoy start -n envoy_1 - -disable-tls --envoy-config-path envoy_config_1.yaml -dh localhost -dp 50051
小贴士或重要提示
看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
copyright@packt.com,并附有链接到该材料。
如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《使用 Python 进行联邦学习》,我们很乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。
前言
下载本书的免费 PDF 副本
感谢您购买本书!
您喜欢在路上阅读,但又无法携带您的印刷书籍到处走吗?
您的电子书购买是否与您选择的设备不兼容?
别担心,现在,每购买一本 Packt 图书,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠活动远不止这些,您还可以获得独家折扣、时事通讯和每天收件箱中的优质免费内容。
按照以下简单步骤获取福利:
- 扫描下面的二维码或访问以下链接

packt.link/free-ebook/978-1-80324-710-6
-
提交您的购买证明
-
就这些!我们将直接将您的免费 PDF 和其他福利发送到您的电子邮件。
第一部分 联邦学习 – 概念基础
在本部分,你将了解大数据 AI 和集中式传统机器学习方法所面临的挑战,以及联邦学习(FL)如何解决它们的主要问题。你将学习 FL 系统的基本概念和运作原理,以及一些机器学习基础知识、分布式系统和计算原则。
本部分包括以下章节:
-
第一章, 大数据和传统 AI 的挑战
-
第二章, 什么是联邦学习?
-
第三章, 联邦学习系统的运作原理
第一章:大数据和传统 AI 的挑战
在本章中,我们将详细解释为什么联邦学习(FL)将成为 2020 年代的关键技术。您将了解大数据是什么,以及从数据隐私、模型偏差和漂移的角度看,它是如何成为问题的。对这些问题及其解决方案的深入理解将激励您开始一段充满挑战的旅程,以获取相关的知识和技能,并使用以下章节来规划 FL 的掌握。阅读本章后,您将明显地看到人工智能(AI)和机器学习(ML)正在发生巨大的范式转变,这是由于公众和商业对当前对大数据导向系统的依赖表示担忧而发生的。无需多言,让我们出发吧!
本章将涵盖以下主题:
-
理解大数据的本质
-
数据隐私作为瓶颈
-
训练数据和模型偏差的影响
-
模型漂移和性能下降
-
FL 作为数据问题的主要解决方案
理解大数据的本质
在 Algorithmia 对 403 位商业领袖进行的2021 年机器学习企业趋势调查中,76%的企业将 AI 和 ML 置于其他 IT 计划之上。COVID-19 全球大流行迫使一些公司加快 AI 和 ML 的发展,正如他们的首席信息官(CIO)所述,83%的调查机构在 AI 和 ML 方面的预算同比增长(YoY),其中四分之一的增长超过 50%。客户体验改善和流程自动化,无论是通过增加收入还是降低成本,都是这一变化的主要驱动力。其他研究,包括 KPMG 的最新报告在 AI 世界中繁荣发展,本质上讲述的是同样的故事。
以深度学习(DL)为代表的 AI 和 ML 发展的持续热潮,得益于过去十年大数据的出现。有了 Apache 的开源软件工具 Hadoop 和 Spark,以及亚马逊网络服务(AWS)、谷歌云平台(GCP)和微软 Azure等云计算服务,私营和公共部门的企业都可以通过处理以前无法想象的大量数据来解决问题。公司和机构不再需要在开发数据分析和模型设计时过于谨慎,以确保相关数据以适当的格式存储。相反,他们可以将可用的原始数据简单地级联到他们的数据湖中,期待他们的数据科学家通过检查它们之间的相关性来发现后续有价值的变量。
大数据似乎是一系列问题的终极解决方案,但正如我们将在以下章节中看到的,它存在一些固有的问题。为了清楚地了解大数据可能存在的问题,让我们首先明确什么是大数据。
大数据的定义
大数据代表大量信息。这些信息现在正以指数级增长。如今,人类每天产生两千万亿字节的数据,这使得大数据变得如此庞大,以至于使用现有的传统数据管理工具来非常有效地处理大数据变得相当困难。以下列出的三个 V 通常用来定义大数据的特征:
-
数量:来自各种来源的数据,如商业交易、物联网(IoT)设备、社交媒体、工业设备、视频等,都为数据的巨大数量做出了贡献。
-
速度:数据速度也是大数据的一个基本特征。通常,数据需要实时或接近实时。
-
种类:数据以所有格式出现,如数值数据、文本文档、图像、视频、电子邮件、音频、金融交易等。
以下截图描述了三个 V 在大数据中的交集:

图 1.1 – 大数据的三个 V
在 1880 年,美国人口普查局从人口普查中收集了大量数据,并估计处理这些数据需要 8 年时间。第二年,一个名叫赫尔曼·霍勒里希的人发明了霍勒里希制表机,这减少了处理数据所需的工作。第一个数据中心建于 1965 年,用于存储指纹数据和税务信息。
大数据现在
数据湖这一概念的出现,在引领我们今天处理数据时看到的巨大规模方面发挥了关键作用。数据湖为公司存储在运营过程中观察到的任意类型的数据提供了完全的自由,消除了否则会阻止公司收集一些最终变得必要的数据的限制。虽然这种自由允许数据湖保持公司生成数据的最大潜力,但它也可能导致一个关键问题——对收集到的数据的理解上的自满。以非结构化方式存储不同类型数据的便利性实际上可能导致“先存储,后整理”的心态。与处理非结构化数据的真正困难实际上源于其处理过程;因此,延迟处理的心态有可能导致数据湖变得非常繁琐,难以筛选和处理,这是由于数据收集的无限制增长。
原始数据的价值仅在于从中可以提取的模型和洞察力。中心数据湖方法导致了一些情况,其中从数据中提取的洞察力受到缺乏结构的限制,从而引发从存储效率低下到由于提取困难导致的实际智能效率低下等一系列问题。另一方面,先于数据湖的方法则因无法访问潜在的大量数据而受到简单限制。FL 允许避免这两类问题,这是 FL 作为推动大数据进入集体智能时代载体的关键驱动支持。
这一主张得到了 FL 将大数据流程从收集→提取智能转变为提取智能→收集的事实支持。对于人类来说,智能可以被视为大量经验的浓缩形式。以类似的方式,在数据源处提取智能——通过在源位置对数据进行训练——简洁地总结了数据,以最大化其实际应用的易用性格式。FL 的后期收集步骤导致在最大数据访问和数据存储效率下创建所需的全球智能。即使是对生成数据源的局部使用,也可以通过大量减少进入剩余数据湖的数据格式数量,从智能和数据联合存储中大大受益。
大数据的 AAA 心态
尽管已经提出了许多定义,强调不同的方面,牛津大学教授维克托·迈尔-舍恩伯格和《经济学人》高级编辑肯尼思·库克耶在 2013 年的国际畅销书《大数据:一场将改变我们生活、工作和思考方式的革命?》中,巧妙地阐明了大数据的本质。大数据并非关于服务器中数据的规模;大数据是关于三个相互关联的心态转变,这些转变相互强化。他们的论点归结为我们可以总结并称之为大数据的AAA 心态,它包括观察的丰富性、对混乱的接受和因果关系的模棱两可。让我们逐一看看它们。
观察的丰富性
从列和行或文件大小来看,大数据不必“大”。大数据有多个观察值,通常称为n,接近或等于感兴趣人群的大小。在传统统计学中,收集整个人群的数据——例如,纽约对健身感兴趣的人——是不可能的或不可行的,研究人员必须从人群中随机选择样本——例如,1000 名对健身感兴趣的纽约人。随机抽样往往很难进行,而且对特定子组的狭窄关注也是难以证明的:在健身房周围调查的人会错过在公园跑步和在家的瑜伽练习者,为什么是健身房会员而不是跑步者和瑜伽爱好者?然而,由于信息和通信技术(ICT)系统的发展和复杂化,今天的研究人员可以通过多个来源访问大约所有人的数据——例如,关于健身的谷歌搜索记录。这种丰富性或n = all的范式是有利的,因为数据所表达的内容可以解释为关于人群的真实陈述,而旧方法只能以显著水平的信心推断这种真理,通常以p 值表示,通常假设小于 0.05。小数据提供统计数据;大数据证明状态。
对杂乱程度的接受
大数据往往比较杂乱。如果我们用谷歌搜索数据作为某人兴趣的代理——例如——我们可能会错误地将他们设备上家人或朋友进行的某些搜索归因于他们,这样估计的兴趣程度将不准确,程度取决于这种非自有设备搜索的比例。在某些设备上,大量的搜索可能由多个用户进行,例如办公室的共用电脑或属于尚未拥有手机的孩子的智能手机。否则,人们可能会搜索在与其他人交谈中出现的词语,而不是自言自语,这并不一定反映他们自己的兴趣。在采用传统方法的研究中,研究人员必须确保这些设备不包括在他们的样本数据中,因为这种“杂乱”会显著影响推理的质量,因为观察的数量会很少。但在大数据研究中并非如此。随着观察数量的增加,其影响会相应减小,直到达到n = all。在大多数设备上,谷歌搜索通常由所有者自主进行,其他上下文中的搜索影响不大。
因果关系的矛盾
大数据通常用于研究相关性而不是因果关系——换句话说,它通常只能告诉我们是什么,而不能告诉我们为什么。对于许多实际问题,仅仅相关性就能提供答案。Mayer-Schönberger 和 Cukier 在《大数据:一场将改变我们生活、工作和思考方式的革命》一书中给出了几个例子,其中之一是 2011 年建立的 Fair Isaac Corporation 的Medication Adherence Score。在人们的行为模式被数据化的时代,收集对感兴趣变量的所有观察结果(n = all)是可能的,并且在这些变量之间发现的关联足够强大,足以指导我们的决策。我们不需要知道人们的一致性或从众性的心理分数,这些分数导致他们遵守医疗处方;通过观察他们在生活中的其他方面的行为,我们可以预测他们是否会遵守处方。
通过拥抱丰饶、接受和矛盾的三重心态,企业和政府已经在从定价服务到推荐产品、优化运输路线和识别犯罪嫌疑人的任务中产生了智能。然而,这种心态在近年来受到了挑战,以下章节将展示这一点。首先,让我们简要了解一下通常被视为理所当然的观察结果的丰饶性目前正面临压力。
数据隐私作为瓶颈
FL 经常被认为是最受欢迎的隐私保护人工智能技术之一,因为生成高质量智能不需要收集或与第三方实体共享隐私数据。因此,在本节中,我们讨论 FL 试图解决以创造高质量智能的数据隐私瓶颈问题。
什么是数据隐私?2021 年 5 月,HCA Healthcare 宣布公司与谷歌达成协议,共享其患者记录和实时医疗数据。各种媒体迅速作出反应,警告公众关于这笔交易,因为谷歌因其在Project Nightingale中的行为而被提及,据称这家科技巨头利用了数百万美国患者的敏感数据。根据皮尤研究中心 2019 年的一项民意调查,超过 80%的公众认为公司收集数据的潜在风险超过了好处,因此如此规模的数据共享项目自然被视为对人们数据隐私的威胁。
数据隐私,也称为信息隐私,是个人控制其个人信息使用方式的权利,这要求第三方在法律允许的范围内妥善处理、处理、存储和使用此类信息。它常与数据安全混淆,数据安全确保数据准确、可靠,并且仅对授权用户可访问。在谷歌账户的情况下,数据隐私规定了公司如何使用账户持有者的信息,而数据安全则要求他们部署诸如密码保护和两步验证等措施。在解释这两个概念时,数据隐私经理使用了一个关于安全和隐私的比喻:安全是一个前提,而隐私则像一扇窗户和一扇窗帘:数据安全是数据隐私的前提。结合起来,它们构成了数据保护,如下面的图表所示:

图 1.2 – 数据安全与数据隐私对比
从前面的图表中我们可以看出,虽然数据安全限制了谁可以访问数据,但数据隐私限制了数据中可以包含什么。理解这种区别非常重要,因为数据隐私可以放大数据安全失败的影响。让我们看看这是如何发生的。
处理私人数据的风险
数据保护失败代价高昂。根据 IBM 的数据泄露成本报告 2021 年,2021 年全球数据泄露的平均成本为美元(USD)4240 万美元,比前一年的 3860 万美元高得多,并且是报告 17 年历史中的最高金额;COVID-19 疫情爆发后远程工作人数的增加被认为是这一激增的主要原因。平均总成本最高的五个行业是医疗保健、金融、制药、技术和能源。该年度近一半的数据泄露包括了客户的个人可识别信息(PII),平均每条记录的成本为 180 美元。一旦客户的 PII 遭到泄露,就会随之而来系统响应期间的系统停机、客户流失、需要获取新客户、声誉损失和商誉下降等负面影响;因此,成本高昂。
IBM 的研究还发现,未能遵守数据保护法规是放大数据泄露成本的主要因素之一(www.ibm.com/downloads/cas/ojdvqgry)。
增加的数据保护法规
随着技术的进步,保护客户数据的需求变得更加重要。消费者在每次交易中都要求并期望得到隐私保护;许多简单的活动都可能危及个人数据,无论是网上银行还是使用手机应用。
全世界各国政府最初对制定法律和法规以保护个人数据免受身份盗窃、网络犯罪和数据隐私侵犯的反应都比较缓慢。然而,现在情况正在改变,因为全球数据保护法律开始成形。
规章法规增加的几个驱动因素包括大量数据的增长,我们需要更多的数据安全和隐私保护来防止用户遭受诸如身份盗窃等恶意活动。
让我们以下面的子部分来看看一些旨在保护数据隐私的措施。
通用数据保护条例(GDPR)
欧洲联盟的通用数据保护条例(GDPR)被认为是现代数据经济中的第一项数据保护法规,并被许多国家效仿以制定自己的法规。GDPR 于 2012 年提出,2016 年由欧盟理事会和议会通过,并于 2018 年 5 月生效。它取代了 1995 年通过的《数据保护指令》。
GDPR 之所以具有划时代意义,在于其强调对个人身份信息(PII)的保护,包括人们的姓名、位置、种族或民族起源、政治或性取向、宗教信仰、协会会员资格以及基因/生物特征/健康信息。欧盟内外组织和个人在处理欧盟居民的个人信息时都必须遵守该法规。GDPR 有七个原则,其中六个是从数据保护指令继承而来的;新的原则是问责制,要求数据使用者保持关于个人数据使用目的和程序的文档。
GDPR 向公众展示了其违规的后果。根据违规的严重程度,GDPR 的罚款可以从全球年营业额的 2%或 1000 万欧元(以较高者为准),或者全球年营业额的 4%或 2000 万欧元(以较高者为准)。2018 年 5 月,成千上万的欧洲人通过法国组织 La Quadrature du Net(也称为英文中的Squaring the Net)对亚马逊公司提起诉讼,指控该公司未经客户同意使用其广告定位系统。经过 3 年的调查,卢森堡的国家数据保护委员会(CNDP)成为全球新闻头条:它对亚马逊开出 7.46 亿欧元的罚款。同样,WhatsApp 在 2021 年 9 月因 GDPR 违规被爱尔兰数据保护委员会罚款;再次,调查历时 3 年,罚款金额为 2.25 亿欧元。
目前,在美国,大多数州已经实施了隐私保护措施,或者很快将实施。此外,一些州已经加强了现有法规,例如加利福尼亚州、科罗拉多州和弗吉尼亚州。让我们逐一了解这些变化。
加利福尼亚消费者隐私法案(CCPA)
加利福尼亚州随后效仿。加利福尼亚消费者隐私法(CCPA)于 2020 年 1 月 1 日起生效。正如其名所示,该法规的目的是像 GDPR 一样保护消费者的个人身份信息(PII)。与 GDPR 相比,CCPA 的适用范围显著缩小。CCPA 仅适用于每年从超过 50,000 个点(该州的居民、家庭或设备)收集数据、年营收超过 2500 万美元或通过出售此类信息获得其一半年度收入的营利性组织。然而,CCPA 违规的成本可能比 GDPR 违规的成本高得多,因为前者对其罚款没有上限(每条无意违规罚款 2500 美元;每条故意违规罚款 7500 美元)。
科罗拉多隐私法(CPA)
根据科罗拉多隐私法(CPA),从 2024 年 7 月 1 日起,数据收集者和控制者必须遵循用户为生成定向广告和销售所选的通用退出规则。这项规则保护科罗拉多州的居民免受定向销售和广告的侵扰,以及某些类型的用户画像。
弗吉尼亚消费者数据保护法(CDPA)
弗吉尼亚州的消费者数据保护法(CDPA)将于 2023 年 1 月 1 日带来多项变更,以增强安全和隐私。这些变更将适用于在弗吉尼亚州或与弗吉尼亚州居民进行业务往来的组织。数据收集者需要获得批准才能利用其私人数据。这些变更还试图确定 AI 供应商的隐私和安全是否充分,这可能需要删除这些数据。
这些只是美国数据法规将如何形成的几个简单例子。这对世界其他地区意味着什么呢?有些人估计,到 2024 年,全球 75%的人口将受到一种或多种隐私法规的保护。
另一个主要数据保护法规的例子是巴西的通用个人数据保护法(LGPD),该法自 2020 年 9 月起生效。它取代了该国与数据隐私相关的数十项法律。LGPD 模仿了 GDPR,其内容几乎相同。在亚洲,日本是第一个引入数据保护法规的国家:2003 年通过的个人信息保护法(APPI)于 2015 年进行了修订。2022 年 4 月,APPI 的最新版本生效,以应对对数据隐私的现代担忧。
佛罗里达州(FL)已被认定为一种关键技术,它能够与不同领域的隐私法规和监管合规性良好地协同工作。
从隐私设计到数据简约主义
组织已经适应了这些规定。TrustArc 的2021 年全球隐私基准调查发现,拥有专门隐私办公室的企业数量正在增加:调查中有 83%的受访者拥有隐私办公室,而 2020 年的比例仅为 67%。85%的企业已经实施了战略性和可报告的隐私管理计划,但其中 73%的人认为他们可以做得更多来保护隐私。他们的热情并不令人惊讶,因为 34%的受访者声称他们在过去 3 年内遭遇过数据泄露,其代价高昂的后果在本章中已提及。隐私办公室将由数据保护官(DPO)领导,负责公司的数据保护影响评估(DPIA),以确保遵守如 GDPR 等要求问责制和个人数据处理文件化的法规。DPO 还负责监督并确保其组织按照法律处理个人数据,而高层管理和董事会应提供必要的支持和资源,以便 DPO 完成其任务。
面对 GDPR,当前数据保护的趋势正在转向数据最小化。在此背景下,数据最小化并不一定鼓励最小化数据的大小;它更直接地涉及最小化数据中的 PII 因素,以便个人不能通过其数据点被识别。因此,数据最小化影响了 AI 行业在创建高性能 AI 应用方面的能力,因为 ML 过程中数据种类的短缺仅仅产生了性能不令人满意的 ML 模型偏差。
本章开头介绍的大数据丰富心态因此受到了公众对数据隐私的关注所约束。违反数据保护法规而被罚款的风险,加上拥有数据坟墓的浪费成本,呼吁实践数据最小化而不是数据丰富。
正因如此,联邦学习(FL)正成为许多 AI 解决方案提供商的必备解决方案,例如医疗行业,它们正在努力应对公众对隐私的关注和数据隐私问题,这基本上成为了一个问题,当第三方实体需要收集私人数据以改善 ML 模型及其应用的质量时。如前所述,FL 是一个有希望的隐私保护 AI 框架,因为数据的学习可以在任何地方进行;即使数据对 AI 服务提供商不可用,我们只需以一致的方式收集和汇总训练好的 ML 模型即可。
现在,让我们考虑大数据 Triple-A 思维模式面临的另一个挑战:接受杂乱无章的数据。
训练数据和模型偏差的影响
大数据量的纯粹数量消除了“垃圾输入,垃圾输出”的险恶现实。或者,是这样吗?事实上,只有当足够的数据可以从各种来源和分布中完全学习,而不造成学习结果的任何偏差时,数据的杂乱无章才能被接受。实际上,在集中位置对大数据进行训练确实需要大量的时间和巨大的计算资源及存储空间。此外,我们可能还需要找到方法来衡量和减少模型偏差,而无需直接收集和访问敏感和私人数据,这可能会与之前讨论的一些隐私法规相冲突。联邦学习(FL)也有分布式和协作学习的方面,这对于消除数据偏差和模型偏差以吸收数据的杂乱无章变得至关重要。通过协作和分布式学习,我们可以显著提高整个学习过程的数据可访问性和效率,这个过程通常既昂贵又耗时。这给了我们突破大数据训练过去所有限制的机遇,以下章节将进行讨论。
大数据昂贵的训练
根据报告:https://www.flexera.com/blog/cloud/cloud-computing-trends-2022-state-of-the-cloud-report,37%的企业每年花费超过 1200 万美元,80%的企业每年花费超过 120 万美元用于公共云。云上的训练成本并不便宜,并且可以假设随着对人工智能和机器学习的需求增加,这种成本将会显著增加。有时,由于以下问题,大数据无法完全用于机器学习训练:
-
大数据存储:大数据存储是一种计算和存储架构,用于收集和管理大量数据集,以供人工智能应用或实时分析使用。全球企业公司仅为了云存储和数据中心成本就支付了超过 1000 亿美元(https://a16z.com/2021/05/27/cost-of-cloud-paradox-market-cap-cloud-lifecycle-scale-growth-repatriation-optimization/)。虽然其中一些数据集对于它们提供的应用至关重要,但它们真正想要的通常是能够从数据中提取的商业智能,而不仅仅是数据本身。
-
显著的训练时间:构建和训练一个可以交付为真实产品的机器学习模型基本上需要大量的时间,这不仅包括训练过程,还包括机器学习管道的准备。因此,在许多情况下,智能的真正价值在机器学习模型交付时就已经丧失了。
-
巨大的计算量:机器学习模型的训练通常消耗大量的计算资源。例如,使用机械手操作如魔方等部件的机器学习任务有时可能需要超过 1000 台计算机。仅运行一些专门的图形芯片几个月,就可能需要十几台机器。
-
通信延迟:为了形成大数据,特别是在云中,需要将大量数据传输到服务器,这本身就会造成通信延迟。在大多数用例中,FL 需要从本地设备或学习环境传输到服务器(称为聚合器)的数据要少得多,该服务器用于综合从这些设备收集的本地 ML 模型。
-
可扩展性:在传统的集中式系统中,由于大数据的复杂性和其昂贵的基础设施(如云服务器环境中的大量存储和计算资源),可扩展性成为一个问题。在 FL 服务器中,仅进行聚合以综合训练多个本地模型以更新全局模型。因此,当 ML 训练以分布式方式在边缘设备上进行时,系统和学习可扩展性显著提高,而不仅仅是单个集中式学习服务器。
FL 有效地利用了可用于 ML 模型轻量级训练的分布式计算资源。无论是实际物理设备上的训练还是云系统虚拟实例上的训练,将模型训练过程并行化到分布式环境中通常可以加速学习本身的速度。
此外,一旦收集了训练好的模型,FL 系统可以快速地将它们综合起来,生成一个更新的 ML 模型,称为全局模型,该模型在边缘侧吸收了足够的经验,因此实现近乎实时的智能传递成为可能。
模型偏差和训练数据
当 ML 算法由于 ML 过程中的错误假设而产生系统性的偏见结果时,就会发生 ML 偏差。ML 偏差有时也被称为算法偏差或 AI 偏差。
2018 年图灵奖得主,因其在深度学习发展方面的杰出贡献而获奖的 Yann LeCun 说:“当数据有偏差时,ML 系统是有偏差的” (twitter.com/ylecun/status/1274782757907030016)。这来自于 Nvidia 团队编译的Flickr-Faces-HQ数据集。基于人脸上采样系统,许多人被分类为白色,因为网络是在主要包含白人图片的Flickr-Faces-HQ数据上预训练的。对于这种误分类人群的问题,模型的架构并不是导致这种输出的关键问题。因此,结论是,一个种族偏差的数据集生成了一个中立的模型来产生偏差的结果。
关于 AI 和机器学习偏差的富有成效的对话是由谷歌前 AI 伦理负责人引领的。2018 年发表的性别阴影论文揭示了主要面部识别模型中的种族和性别偏差,国会的立法者寻求禁止美国联邦政府使用这项技术。包括亚马逊、IBM 和微软在内的科技公司也同意暂停或终止向警方销售面部识别模型。他们鼓励采取干预主义的数据收集方法,建议科学家和工程师明确模型开发的目标,制定严格的数据收集政策,并对收集到的数据进行彻底评估,以避免偏差——详细信息可在FATE/CV网站上找到(https://sites.google.com/view/fatecv-tutorial/home)。
FL 可能是克服数据孤岛问题的最有前途的机器学习技术之一。很多时候,数据甚至无法访问或用于训练,导致数据和模型存在重大偏差。自然地,FL 通过解决数据隐私和孤岛问题,这些问题成为避免数据偏差的瓶颈,因此 FL 对于克服偏差非常有用。在这种情况下,FL 正在成为大数据服务和应用的突破性实现,这在arxiv.org/pdf/2110.04160.pdf中得到了彻底的研究。
此外,还有一些技术试图在 FL 本身中减轻模型偏差,例如重新加权和偏见消除器,这些都在arxiv.org/pdf/2012.02447.pdf中详细说明。
模型漂移和性能退化
模型漂移通常是指由于数据以及输入和输出变量之间关系的变化而导致机器学习模型性能下降,也称为模型退化。模型漂移可以通过持续学习来应对,以适应数据集或环境中的最新变化,几乎在实时进行。FL 的一个重要方面是通过在本地分布式环境中学习发生时即时更新机器学习模型,以实现一个连续学习框架。这样,FL 可以解决企业 AI 应用中常见的智能在交付生产时变得无用的状况。
我们现在将讨论模型如何退化或停止工作,以及一些当前模型运维(ModelOps)的努力,以持续提高模型性能并实现可持续的 AI 运营。
模型如何停止工作
任何具有固定参数或权重的 AI 和 ML 模型,这些参数或权重是从训练数据中生成的,并调整到测试数据,当在模型接收的数据类似于训练和测试数据的环境中部署时,可以表现相当好。如果一个自动驾驶模型在晴朗的白天记录的数据上得到了良好的训练,那么该模型可以在晴天安全地驾驶车辆,因为它正在做它被训练去做的事情。然而,在一个雨天晚上,如果车辆是自动驾驶的,那么没有人应该坐在或靠近车辆:模型被喂以完全陌生的、黑暗和模糊的图像;其决策将完全不可靠。在这种情况下,模型的决策将偏离轨道,因此得名模型漂移。再次强调,如果模型在类似于训练和测试环境的环境中部署,并且环境在一段时间内没有显著变化,那么模型漂移不太可能发生。但在许多商业情况下,这个假设并不总是成立,模型漂移成为一个严重的问题。
模型漂移有两种类型:数据漂移和概念漂移。数据漂移发生在部署的模型输入数据与模型训练数据显著不同时。换句话说,数据分布的变化是数据漂移的原因。上述日间自动驾驶模型在夜间表现不佳就是一个数据漂移的例子。另一个例子是,在加利福尼亚州训练的冰淇淋销售预测模型在新西兰部署;南半球的季节性与北半球相反,因此预计的冰淇淋销量在夏季会低,在冬季会高,这与实际销量相反。
相反,概念漂移是由于变量之间相关性变化的结果。在统计学的术语中,这意味着数据生成过程已经改变。这正是Google Flu Trends (GFT)所遭遇的,正如《潜行者经济学家》一书的作者在以下《金融时报》文章中所述:https://www.ft.com/content/21a6e7d8-b479-11e3-a09a-00144feabdc0#axzz30qfdzLCB。
在此时期之前,搜索查询与流感传播之间存在有意义的关联,因为主要是那些怀疑自己感染了流感的人会在浏览器中输入这些词语,因此模型运作成功。但在 2013 年,这种情况可能不再成立,因为其他类别的人,例如那些对潜在的大流行病持预防态度的人或只是好奇的人,也在搜索这些词语,他们可能是由谷歌的推荐引导去搜索的。这种概念漂移可能导致 GFT 高估了与疾病控制与预防中心(Centers for Disease Control and Prevention,CDC)提供的医疗报告相比的传播情况。
不论是通过数据还是通过概念,模型漂移会导致模型性能下降,这是由于我们关注相关性所致。在数据科学领域的术语中,“ground truth”并不像物理学和化学等硬科学中的普遍真理那样,即因果关系。它仅仅是对给定数据中变量在特定环境中如何相互关联的真实陈述,并且不能保证当环境发生变化或不同时,相关性仍然存在。也就是说,我们估计的“ground truth”可能随时间和地点而变化,就像“ground”在历史上和地理上被地震事件重塑一样。
持续监控——放弃因果关系的代价
在 Redis Labs(https://venturebeat.com/business/redis-survey-finds-ai-is-stressing-it-infrastructure-to-breaking-point/)委托的一项调查中,大约一半的受访者将模型可靠性(48%)、模型性能(44%)、随时间变化的准确性(57%)和运行模型的延迟(51%)列为部署模型时面临的主要挑战。鉴于模型漂移的风险和担忧,AI 和机器学习模型的相关方在部署后需要完成两项额外的工作。首先,必须持续监控模型性能以检测模型漂移。数据漂移和概念漂移可能逐渐或突然发生。一旦检测到模型漂移,就需要使用新的训练数据重新训练模型,当发生概念漂移时,甚至可能需要使用新的模型架构来升级模型。
为了解决这些需求,一个新的机器学习原则,称为持续交付机器学习(Continuous Delivery for Machine Learning,简称CD4ML)已被提出。在 CD4ML 的框架中,模型首先使用训练数据进行编码和训练。然后,使用单独的数据集对模型进行测试,并根据某些指标进行评估,通常情况下,从多个候选模型中选择最佳模型。接下来,选定的模型通过进一步的测试进行生产化,以确保模型在部署后表现良好,一旦通过测试,它就会被部署。在这里,监控过程开始。当观察到模型漂移时,模型将使用新数据重新训练或根据漂移的严重程度给予新的架构。如果你熟悉软件工程,你可能会注意到 CD4ML 是机器学习领域对持续集成/持续交付(Continuous Integration/Continuous Delivery,简称CI/CD)的采用。在类似的方式中,源自开发-运维(Development-Operations,简称DevOps)软件工程框架的 AI 和机器学习操作框架ModelOps正在获得越来越多的关注。ModelOps 连接了机器学习操作(Machine Learning Operations,简称MLOps:数据工程与数据科学的集成)和应用程序工程;它可以被视为 CD4ML 的推动者。
大数据“AAA 思维模式”的第三个因素让我们能够专注于相关性,并在过去十年中帮助快速构建 AI 和 ML 模型。发现相关性比发现因果关系要容易得多。对于许多多年来一直在告诉我们从人们的谷歌搜索模式中需要知道什么的人工智能和机器学习模型,我们必须检查它们今天是否仍然有效。明天也是如此。
正因如此,联邦学习(FL)是持续学习的重要方法之一。在创建和运营 FL 系统时,还重要的是要开发具有 ModelOps 功能的系统,因为 FL 的关键作用是以协作方式不断从各种学习环境中改进模型。甚至可以使用 FL 实现一个众包学习框架,以便平台上的用户可以将所需的 ML 模型带到本地进行适应和训练,并将更新的模型返回给 FL 服务器,由聚合器处理。通过一个高级模型聚合框架来过滤掉可能降低当前模型性能的有毒 ML 模型,FL 可以持续整合其他学习成果,从而实现一个可持续的持续学习操作,这对于具有 ModelOps 功能的平台至关重要。
FL 作为数据问题的主要解决方案
到目前为止,在本章中,我们确认了大数据存在需要解决的问题。必须保护数据隐私,不仅是为了保护个人,也是为了保护可能面临数据泄露和随之而来的罚款的数据用户。一组大数据中的偏差可以通过代理对推断产生重大影响,即使省略了关于性别和种族的因素,而且专注于相关性而不是因果关系使得预测模型容易受到模型漂移的影响。
在这里,让我们从架构、流程、问题和好处等方面讨论传统大数据 ML 系统和 FL 系统之间的区别。以下图表展示了传统大数据 ML 系统和 FL 系统之间的视觉比较:
![Figure 1.3 – 传统大数据 ML 系统和 FL 系统比较]
![img/B18369_01_03.jpg]
图 1.3 – 传统大数据 ML 系统和 FL 系统比较
在传统的数据系统中,数据被收集以创建大型数据存储。这些大型数据存储用于使用机器学习解决特定问题。由于训练数据量庞大,产生的模型显示出强大的泛化能力,并最终被部署。
然而,持续的数据收集需要大量的通信带宽。在注重隐私的应用中,数据的传输可能完全被禁止,这使得模型创建成为不可能。在大数据存储上训练大型 ML 模型计算成本高昂,传统的集中式训练效率受限于单机性能。缓慢的训练过程导致增量模型更新之间的延迟时间过长,导致无法灵活地适应新的数据趋势。
另一方面,在联邦学习系统中,机器学习训练直接在数据所在地进行。生成的训练模型被收集到中央服务器上。使用聚合算法从收集到的模型中生成一个聚合模型。聚合模型被发送回数据所在地进行进一步训练。
联邦学习方法通常需要在分布式系统设置中设置和维护训练性能,这会产生额外的开销。然而,即使架构和设置稍微复杂一些,也有其卓越的益处。训练在数据所在地进行,因此数据永远不会被传输,从而维护数据隐私。训练可以在多个节点上异步进行,这导致高效且易于扩展的分布式学习。仅在服务器和节点之间传输模型权重,因此联邦学习在通信方面是高效的。高级聚合算法甚至可以在受限场景中保持训练性能,并在标准机器学习场景中提高效率。
大多数人工智能项目似乎都无法交付,或者根本无法达到预期效果。要交付一个真正的人工智能应用和产品,之前讨论的所有问题都需要认真考虑。显然,联邦学习(FL)与其他关键技术的结合,用于处理机器学习(ML)管道和引擎处理的数据,正逐渐成为解决数据相关问题的连续和协作的关键解决方案。
我们如何利用人工智能和机器学习的力量,优化整个社会的技术系统——即,在数据最小化和道德的基础上,带来更加快乐、舒适、便捷和安全的世界,并持续进行改进?我们认为关键在于一个集体智慧或以智能为中心的平台,这在第十章“未来趋势和发展”中也有讨论。在本书的后续章节中,我们介绍了联邦学习系统的概念、设计和实现,作为一种有前景的技术,用于通过人工智能和机器学习模型的网络来协调集体智慧,以满足之前讨论的需求。
摘要
本章概述了联邦学习如何通过首先理解大数据的定义及其本质,包括大量的观察、接受混乱和因果关系的矛盾性,来潜在地解决许多大数据问题。
我们从许多地区了解了隐私法规的多种形式,以及数据泄露和隐私侵犯的风险,这些最终导致利润损失,以及创建真正人工智能应用的瓶颈。联邦学习,按照设计,不会收集任何原始数据,可以保护数据隐私并遵守这些法规。
此外,使用 FL 框架,我们可以减少影响 ML 模型性能的固有偏见,并通过持续学习框架最小化模型漂移。因此,需要一个基于 FL 的分布式和协作学习框架,以实现更经济高效的方法。
这本导论章节以联邦学习(FL)作为解决上述大数据问题的主要解决方案的潜力结束,这一解决方案基于集体智能的范式转变思想,这种思想有可能取代当前主流的数据中心平台。
在下一章中,我们将看到联邦学习(FL)在数据科学领域中的位置以及它如何开启机器学习(ML)的新时代。
进一步阅读
要了解更多关于本章所涉及的主题,请参阅以下参考文献:
-
Algorithmia. (2021 年). 2021 年机器学习企业趋势. 西雅图:Algorithmia.
-
Mayer-Schönberger, V. 和 Cukier, K. (2013 年). 大数据:一场将改变我们生活、工作和思考方式的革命. 波士顿/纽约:Eamon Dolan/Houghton Mifflin Harcourt.
-
《经济学人》. (2010 年 2 月 27 日). 数据无处不在. 《经济学人》.
-
数据隐私经理. (2021 年 10 月 1 日). 数据隐私与数据安全[定义和比较]. 《数据隐私经理》.
-
IBM. (2021 年). 2021 年数据泄露成本报告. 纽约:IBM.
-
Burgess, M. (2020 年 3 月 24 日). 什么是 GDPR?英国 GDPR 合规的总结指南. 《Wired》.
-
TrustArc. (2021 年). 2021 年全球隐私基准调查. 沃尔纳特克里克:TrustArc.
-
Auxier, B.,Rainie, L.,Anderson, M.,Perrin, A.,Kumar, M. 和 Turner, E. (2019 年 11 月 15 日). 美国人和隐私:担忧、困惑,感觉无法控制他们的个人信息. 皮尤研究中心.
-
Hes, R. 和 Borking, J. (1995 年). 隐私增强技术:通往匿名之路. 荷兰海牙:安大略省信息和隐私专员.
-
Goldsteen, A.,Ezov, G.,Shmelkin, R.,Moffie, M. 和 Farkash, A. (2021 年). 机器学习模型中 GDPR 合规的数据最小化. 《AI 和伦理》,1-15 页。
-
Knight, W. (2019 年 11 月 19 日). 苹果卡没有“看到”性别——这就是问题所在. 《Wired》.
-
Gebru, T. 和 Denton, E. (2020 年). CVPR 2020 计算机视觉公平性、责任、透明度和伦理教程. 在线可用:
sites.google.com/view/fatecv-tutorial/home. -
Ukanwa, K. (2021 年 5 月 3 日). 算法偏见不仅不公平,而且对商业有害. 《波士顿环球报》.
-
O’Neil, C. (2016 年). 数学破坏武器:大数据如何加剧不平等并威胁民主. 纽约:Crown.
-
Blackman, R. (2020 年 10 月 15 日). 构建道德 AI 的实用指南. 《哈佛商业评论》.
-
Ginsberg, J.,Mohebbi, M.,Patel, R.,Brammer, L.,Smolinski, M. S. 和 Brilliant, L. (2009). 利用搜索引擎查询数据检测流感大流行。自然,第 457 卷,第 1012-1014 页。
-
Anderson, C. (2008 年 6 月 23 日). 理论的终结:数据洪流使科学方法过时。Wired。
-
Butler, D. (2013). 当谷歌误判流感时。自然,第 494 卷,第 155-156 页。
-
Harford, T. (2014 年 3 月 28 日). 大数据:我们是否犯了大错误?。金融时报。
-
Dral, E. 和 Samuylova, E. (2020 年 11 月 12 日). 机器学习监控,第五部分:为什么你应该关注数据漂移和概念漂移。Evidently AI 博客。
-
Forrester Consulting。(2021). 将 ML 模型部署到内存数据库:实现快速性能。从
redis.com/wp-content/uploads/2021/06/forrester-ai-opportunity-snapshot.pdf检索。 -
Sato, D.,Wider, A. 和 Windheuser, C. (2019 年 9 月 19 日). 机器学习的持续交付:自动化机器学习应用的端到端生命周期。从martinFowler.com检索,
martinfowler.com/articles/cd4ml.html。 -
Verma, D. C. (2021). 联邦 AI 应用于现实世界商业场景。纽约:CRC 出版社。
-
Bostrom, R. P. 和 Heinen, J. S. (1977). MIS 问题与失败:社会技术视角。第一部分:原因。MIS 季刊,第 1 卷第 3 期,第 17 页。
-
Weld, D. S.,Lin, C. H. 和 Bragg, J. (2015). 人工智能与集体智慧。集体智慧手册,第 89-114 页。
-
Abay, A.,Zhou, Y.,Baracaldo, N.,Rajamoni, S.,Chuba, E. 和 Ludwig, H. 缓解联邦学习中的偏差。可在 https://arxiv.org/pdf/2012.02447.pdf 获取。
-
《大数据:一场将改变我们生活、工作和思考方式的革命》 (
www.amazon.com/Big-Data-Revolution-Transform-Think/dp/0544227751。
第二章:什么是联邦学习?
在第一章《大数据和传统人工智能的挑战》中,我们探讨了大数据和机器学习(ML)的潮流如何为新的实用 ML 应用方法奠定了基础。本章将联邦学习(FL)视为满足这种新 ML 方法需求的答案。简而言之,FL 是一种 ML 方法,允许模型在数据源之间并行训练,而不需要传输任何数据。
本章的目标是建立 FL 方法的论据,解释必要的概念构建块,以确保你可以达到对 FL 的技术方面和实际应用的类似理解。
阅读本章后,你应该对 FL 过程有一个高级别的理解,并且能够可视化这种方法在现实世界问题领域中的位置。
在本章中,我们将涵盖以下主题:
-
理解当前机器学习(ML)的状态
-
分布式学习特性——走向可扩展人工智能
-
理解联邦学习(FL)
-
FL 系统考虑因素
理解当前机器学习(ML)的状态
为了理解从应用 FL 中获得的利益为何可能超过这种方法增加的复杂性,有必要了解 ML 当前的实践及其相关的限制。本节的目标是为你提供这个背景。
什么是模型?
术语“模型”在众多不同学科中都有应用;然而,我们感兴趣的广义定义可以缩小到某些所需系统内动态的工作表示。简单来说,我们通过 B 模型更好地理解现象 A,A 是现象 B 的某种现象,这是通过 B 提供的增加的交互性来实现的。考虑一个物体从真空中某个点掉落的现象。使用运动方程,我们可以精确计算出物体落地所需的时间——这就是上述现象的模型。
这种方法的强大之处在于,可以在不与所讨论的现象进行明确交互的情况下观察创建的模型的结果。例如,下落物体的模型使我们能够在某个高度上确定 10 公斤物体和 50 公斤物体下落时间的差异,而无需在真实真空中从该高度物理地释放真实物体。显然,对自然现象的建模在能够声称真正理解这些现象方面发挥着关键作用。消除对现象进行全面观察的需要,使得在决策过程中实现真正的泛化成为可能。
在计算机科学领域,模型的概念被大大缩小了。在这种情况下,模型是算法,它允许根据对所讨论现象的某些初始描述输出该现象的一些关键值。回到落体例子,计算机科学模型可能包括根据物体的质量和下落的高度计算值,如击中地面的时间和最大速度。这些计算机科学模型由于计算机在从无数起始现象配置中计算输出的超人类能力而独具特色,为我们提供了更深入的理解和更广泛的概括。
那么,我们如何创建这样的模型呢?第一个也是最简单的方法是构建基于规则的系统或白盒模型。白盒模型(也称为玻璃盒或透明盒)是通过明确写出感兴趣系统的底层函数来构建的。这只有在系统信息可预先获得的情况下才可能。自然地,在这种情况下,底层函数相对简单。一个这样的例子是分类随机选择的整数是奇数还是偶数的问题;我们可以很容易地编写一个算法通过检查整数除以二的余数来完成这个任务。如果你想看看加满油箱需要花多少钱,给定油箱的空余量和每加仑的价格,你只需将这些值相乘即可。尽管这些例子很简单,但它们说明了简单的模型可以在各个领域有大量的实际应用。
不幸的是,底层函数的白盒建模很快就会变得过于复杂而无法直接执行。一般来说,系统通常过于复杂,我们无法构建白盒模型。例如,假设你想预测你财产的未来价值。你有很多关于财产的指标,比如面积、它的年龄、位置,以及利率等等。你相信财产价值和所有这些指标之间可能存在线性关系,即所有这些指标的加权总和将给出财产价值。现在,如果你实际上基于这个假设尝试构建白盒模型,你必须直接确定每个指标的参数(权重),这意味着你必须知道房地产定价系统的底层函数。通常情况下,这不是事实。因此,我们需要另一种方法:黑盒建模。
ML – 自动化模型创建过程
黑盒系统的概念最早在二战期间的电气电路领域发展起来。正是著名的控制论学家诺伯特·维纳开始将黑盒视为一个抽象概念,并在 20 世纪 60 年代由马里奥·奥古斯特·邦格建立了通用理论。如前所述,估计未来房地产价值的函数是一个很好的黑盒例子。正如你所期望的,这个函数足够复杂,我们尝试编写一个白盒模型来表示它是不可行的。这就是机器学习发挥作用的地方,它允许我们创建一个作为黑盒的模型。
参考文献
你可能知道,黑盒建模因其缺乏可解释性而受到批评,这是本书范围之外的一个重要概念;我们推荐 Serg Masís 所著的 Packt 出版的《Python 可解释机器学习》作为该主题的参考。
机器学习是一种人工智能,用于自动生成用于决策和预测的模型参数。图 2.1 以非常简单的方式说明了这一点:那些已知值和未知值之间存在线性关系的案例允许应用一个流行的算法,称为普通最小二乘法(OLS)。OLS 通过找到产生最接近预测的一组参数来计算线性关系的未知参数,这些参数是在一些已知示例(输入特征值集和真实输出值对)上进行的:
![图 2.1 – 机器学习确定模型参数]
![图片 B18369_02_01.jpg]
![图 2.1 – 机器学习确定模型参数]
上述图表显示了一个简单的二维线性回归问题,包含一个特征/输入变量和一个输出变量。在这个简单的二维案例中,我们可能相对容易直接提出代表最佳拟合关系的参数,无论是通过隐含知识还是通过测试不同的值。然而,很明显,随着特征变量的数量增加,这种方法很快就会变得难以处理。
最小二乘法(OLS)允许我们从相反的方向解决这个问题:我们不是产生线性关系并在数据上评估它们,而是可以直接使用数据来计算最佳拟合关系的参数。回顾房地产问题,假设我们已经收集了大量房地产估值数据点,包括相关的指标值和销售价格。我们可以应用 OLS 来取这些点,并找出每个指标与任何房地产销售价格之间的关系(仍然假设真实关系是线性的)。据此,我们可以输入我们房地产的指标值,并得到预测的销售价格。
这种方法的力量在于将这种关系计算从对问题的任何隐含知识中抽象出来。最小二乘法(OLS)算法并不关心数据代表什么——它只是找到给定数据的最佳直线。这类方法正是机器学习所包含的,它赋予了在没有内部关系知识的情况下,仅凭足够的数据创建现象模型的能力。
简而言之,机器学习(ML)让我们能够编写算法,这些算法可以从数据中学习创建模型,而我们这样做的原因是为了近似复杂系统。重要的是要记住,由于外部因素的影响,复杂系统的底层函数可能会随时间而改变,这使得从旧数据中创建的模型很快就会过时。例如,前面的线性回归模型可能无法用于估计遥远未来或遥远地区的财产价值。在只包含几十个参数的模型中,并没有考虑到这种宏观尺度上的变化,我们需要为相邻数据点的不同组别使用不同的模型——除非我们采用更复杂的机器学习方法,如深度学习。
深度学习
那么,深度学习是如何在普通用法中与机器学习(ML)同义的?深度学习涉及应用深度神经网络(DNN),这是一种受大脑中神经元之间信号传递启发的超参数化模型。深度学习的基础是在 20 世纪 60 年代初由 Frank Rosenblatt 建立的,他被誉为深度学习之父。他的工作在 20 世纪 70 年代和 80 年代得到了包括 Geoffrey Hinton、Yann LeCun 和 Yoshua Bengio 在内的计算机科学家的进一步发展,而“深度学习”这一术语是由加州大学欧文分校的杰出教授 Rina Dechter 普及的。与简单的机器学习算法(如线性回归)相比,深度学习可以执行更复杂的任务。虽然具体内容超出了本书的范围,但深度学习能够解决的关键问题是复杂非线性关系的建模,由于它提供的建模能力增强,推动了机器学习作为整体在众多领域的最前沿。
这种能力已经通过针对不同模型大小案例的具体通用逼近定理在数学上得到证明。对于那些对深度学习或机器学习(ML)总体上都是新手的人来说,Sebastian Raschka 和 Vahid Mirjalili 合著的《Python 机器学习》第三版是一个很好的参考资料,可以了解更多关于这个主题的内容。
在过去十年中,随着大数据的背景,科技巨头们构建了越来越强大的模型,正如在第一章“大数据与传统人工智能的挑战”中讨论的那样。如果我们看看当今最先进的深度学习模型,它们可能拥有多达万亿个参数;预期地,这赋予了它们在建模复杂函数方面无与伦比的灵活性。深度学习模型之所以能够通过所谓的双重下降现象任意扩展以增加性能,而不同于之前使用的其他机器学习模型类型,是因为这种现象。这指的是某个参数化/训练阈值能够克服标准的偏差-方差权衡(其中增加复杂性会导致对训练数据的微调,减少偏差但增加方差),并继续提高性能。
关键的启示是,深度学习模型的性能可以被认为是仅受限于可用的计算能力和数据,这两个因素在过去十年中由于计算技术的进步以及设备数量和收集数据的软件数量的不断增加而迅速增长。深度学习已经与机器学习交织在一起,深度学习在当前的机器学习和大数据状态下发挥着重要作用。
本节重点在于确立当前机器学习技术所执行建模的重要性。从某种意义上说,这可以被认为是什么——FL(联邦学习)究竟试图做什么。接下来,我们将关注哪里,即众多机器学习应用所期望的设置。
分布式学习特性——向可扩展人工智能迈进
在本节中,我们介绍了分布式计算环境,并讨论了该环境与机器学习方法的交汇,以完全确立 FL(联邦学习)必要性的支持。本节的目标是让用户理解分布式计算环境带来的优势和局限性,以便理解 FL 如何解决其中的一些局限性。
分布式计算
在过去几年中,从分布式计算的角度来看,新方法的开发和对现有服务器基础设施的转换呈现出大幅但可预测的增长。进一步概括,分布式方法本身越来越多地从研究实现转向在生产环境中的广泛应用;这一现象的一个显著例子是亚马逊的 AWS、谷歌的Google Cloud Platform(GCP)和微软的 Azure 等云计算平台的运用。结果是,按需资源的灵活性使得在许多其他情况下,可能会受到本地服务器和计算能力的瓶颈限制的应用中,实现了成本节约和效率提升。
虽然不能完全将云计算与分布式计算的概念相提并论,但由此产生的关键好处在本质上相似。从高层次来看,分布式计算涉及将某些计算任务的必要工作分散到多个计算代理中,以便每个代理都能近乎自主地行动。以下图显示了在回答问题的高层次上下文中,集中式和分布式方法之间的差异:

图 2.2 – 集中式与分布式问答
在这个玩具示例中,集中式方法涉及按顺序处理输入问题,而分布式方法能够同时处理每个问题。应该清楚的是,并行方法是在计算资源使用和增加回答速度之间进行权衡。那么,这个权衡对于现实世界的应用是否有益,自然就成为了一个问题。
现实世界案例 – 电子商务
为了理解分布式计算方法的实际好处,让我们通过传统计算和分布式计算的角度分析一个业务问题。考虑一个试图使用本地服务器托管其网站的电子商务业务。传统的方法是在业务方面进行足够分析,以确定未来某个时间点的预期流量量,并投资购买一台或几台足够处理该计算出的流量的服务器机器。
几个案例立即揭示了这种方法的缺陷。考虑一种情况,即网站的使用量远远超过了最初的预测。固定数量的服务器意味着所有升级都必须是硬件升级,导致必须购买且不再使用的旧硬件。进一步来说,现在增加的使用量是否保持不变也没有保证。使用量的进一步增加将导致更多的扩展成本,而使用量的减少将导致资源浪费(当小型机器就足够时,仍需维护大型服务器)。一个关键点是,由于使用单机方法来管理托管,额外服务器的集成并不简单。此外,我们必须考虑处理大量并发请求的硬件限制。每个机器处理并发请求的能力有限——大量的流量几乎肯定会最终成为瓶颈,无论每个服务器的可用功率如何。
相比之下,考虑基于分布式计算的解决方案。根据初步的商业预测,购买了一些较小的服务器机器,并且每台机器都设置好了以处理一些固定的流量量。如果出现超出预期的流量情况,不需要对现有机器进行修改;相反,可以购买更多类似尺寸的服务器,并配置它们来处理指定的新流量量。如果流量减少,相应数量的服务器可以被关闭或转移到处理其他任务。这意味着相同的硬件可以用于可变流量的处理。
这种能力能够快速扩展以处理任何时刻必要的计算任务,这正是分布式计算方法允许计算代理无缝开始和停止工作在所述任务上的原因。此外,与使用较少的大型机器相比,并行使用许多较小的机器意味着可以同时处理的请求数量显著更高。很明显,在这种情况下,分布式计算方法本身具有节省成本和灵活性的优势,这是更传统的方法无法比拟的。
分布式计算的好处
通常,分布式计算方法为任何计算任务提供了三个主要好处——可扩展性、吞吐量和弹性。在前面的网页托管案例中,可扩展性指的是根据进入的流量量调整部署的服务器数量,而吞吐量指的是通过较小服务器的固有并行性来减少请求处理延迟的能力。在这个例子中,弹性可能指的是其他部署的服务器能够承担停止工作的服务器的负载,从而使得托管能够相对不受影响地继续进行。
分布式计算通常在处理大量数据时得到应用,尤其是在使用单一机器进行数据分析在计算上不可行或不太理想的情况下。在这些情况下,可扩展性允许根据所需的运行时间和任何给定时间的数据量等因素部署可变数量的代理,而每个代理能够自主地并行处理数据子集的能力,使得处理吞吐量对于单一高性能机器来说是不可能的。结果发现,这种不依赖于尖端硬件的做法进一步降低了成本,因为硬件价格与性能比通常不是线性的。
虽然开发用于在分布式计算环境中运行的并行化软件并非易事,但希望这清楚地表明,许多实际计算任务都极大地受益于这种方法实现的可扩展性和吞吐量。
分布式机器学习
当考虑那些在实用应用中已被证明有价值且可能直接受益于增加可扩展性和吞吐量的计算任务类型时,很明显,快速增长的机器学习领域几乎处于顶端。事实上,我们可以将机器学习任务视为上述分析大量数据任务的具体例子,强调正在处理的数据和分析所执行的性质。廉价计算能力(例如,智能设备)的联合增长以及数据分析建模的既定益处,导致了一些公司拥有大量数据存储,并希望从这些数据中提取有意义的见解和预测。
第二部分正是机器学习旨在解决的问题,已经在各个领域完成了大量工作。然而,与其他计算任务一样,在大量数据上执行机器学习往往会导致时间-计算能力权衡,需要更强大的机器在合理的时间内完成这些任务。随着机器学习算法在计算和内存方面变得更加密集,例如最近具有数十亿参数的最新深度学习模型,硬件瓶颈使得增加计算能力变得不可行。因此,当前的机器学习任务必须应用分布式计算方法,以保持前沿地位,同时在使用时间内产生结果。
边缘推理
尽管前面描述的深度学习的普及,以及在第第一章中讨论的从大数据到集体智能的范式转变——“大数据与传统人工智能的挑战”,为分布式机器学习提供了足够的动力,但其物理基础却源于最近边缘计算的快速发展。这里的边缘指的是部署解决方案的附近区域;因此,边缘计算指的是在数据源附近或其处处理数据。将计算的概念扩展到机器学习导致边缘人工智能(Edge AI)的想法,其中模型直接集成到边缘设备中。一些流行的例子包括亚马逊 Alexa,其中边缘人工智能负责语音识别,以及自动驾驶汽车,它们收集真实世界的数据,并通过边缘人工智能逐步改进。
最普遍的例子是智能手机——一些潜在用途包括向用户推荐内容、带有语音助手的搜索和自动完成、自动将图片排序到相册和图库搜索等。为了利用这种潜力,智能手机制造商已经开始将专注于机器学习的处理器组件集成到他们与最新手机集成的芯片中,例如三星的神经处理单元和谷歌 Tensor 芯片上的张量处理单元。谷歌还通过他们的Android ML Kit SDK为 Android 应用程序开发了专注于机器学习的 API。从这一点来看,机器学习应用正转向边缘计算范式。
假设智能手机需要使用深度学习模型来进行单词推荐。这样,当你用手机输入文字时,它会为你提供下一个单词的建议,目的是节省你的时间。在集中式计算过程的方案中,中央服务器是唯一可以访问这个文本预测模型的组件,而没有任何一部手机本地存储了这个模型。中央服务器处理来自手机的所有请求,以返回单词推荐。当你输入时,你的手机必须将已输入的内容以及一些关于你的个人信息发送到中央服务器。服务器接收到这些信息,使用深度学习模型进行预测,然后将结果发送回手机。以下图反映了这个场景:

图 2.3 – 集中式推理场景
当你观察这个场景时,会出现一些明显的问题。首先,即使只有半秒到一秒的延迟,推荐的速度也会比你自己输入所有内容慢,使得系统变得无用。此外,如果没有互联网连接,推荐根本无法工作。这个方案的另一个限制是需要中央服务器处理所有这些请求。想象一下世界上有多少部智能手机正在使用,你就会意识到由于这个解决方案的极端规模,其可行性存在不足。
现在,让我们从边缘计算的角度来看同一个问题。如果智能手机本身包含深度学习模型会怎样?中央服务器只负责管理最新的训练模型,并与每部手机通信这个模型。现在,无论何时你开始输入,你的手机都可以使用接收到的模型在本地进行推荐。以下图反映了这个场景:

图 2.4 – 边缘推理场景
这样既消除了延迟问题,又避免了在中央位置处理传入的推理请求的需要。此外,手机不再需要与服务器保持连接以进行推荐。每部手机负责满足其用户的请求。这是边缘计算的核心优势:我们将计算负载从中央服务器转移到了边缘设备/服务器。
边缘训练
集中式与分布式计算之间的区别可以扩展到模型训练的概念。让我们继续以智能手机为例,但思考一下我们如何训练预测模型。首先,在集中式机器学习过程中,用于训练推荐模型的所有数据都必须从用户的设备中收集并存储在中央服务器上。然后,收集到的数据用于训练一个模型,最终发送到所有手机。这意味着中央服务器仍然需要能够处理大量涌入的用户数据,并以高效的方式存储它,以便能够训练模型。
这种设计导致了集中式计算方法中存在的问题:随着连接到服务器的手机数量的增加,服务器处理传入数据的能力需要扩展,以维持训练过程。此外,由于数据需要在这种方法中集中传输和存储,因此始终存在传输被拦截或存储数据受到攻击的可能性。在许多情况下,需要或强烈希望保护数据机密性和隐私;例如,金融和医疗行业中的应用。因此,集中式模型训练限制了用例,需要一种直接在边缘设备上处理数据的工作方式。这正是 FL 的动机所在。
理解 FL
本节重点在于提供对 FL(联邦学习)如何作为解决上一节所述问题设置的一种解决方案的高级技术理解。本节的目标是让您了解 FL 作为解决方案的适用性,并为后续章节提供概念基础。
定义 FL
联邦学习是一种从边缘训练的本地模型中综合全局模型的方法。FL 首次由 Google 在 2016 年为他们的 Gboard 应用开发,该应用结合了 Android 用户打字历史的上下文来建议更正并提出后续单词的候选词。确实,这正是我们在边缘推理和边缘训练部分讨论的精确单词推荐问题。Google 提出的解决方案是一种去中心化的训练方法,其中迭代过程会在边缘计算模型训练更新,并将这些更新聚合起来以生成应用于模型的全球更新。这种聚合模型更新的核心概念对于从边缘训练产生单个、高性能的模型至关重要。
让我们进一步分解这个概念。期望的模型分布在边缘,并在边缘收集的本地数据上训练。当然,我们可以预期,在一个特定数据源上训练的模型不会代表整个数据集。因此,我们将使用有限数据训练的这种模型称为本地模型。这种方法的直接好处是,它使得在集中式情况下由于隐私和效率问题而无法收集的数据上的机器学习成为可能。
聚合,FL 的关键理论步骤,允许我们期望的单一全局模型从某些迭代产生的本地模型集合中创建出来。最著名的聚合算法,因其简单性和出人意料的性能而受到欢迎,被称为联邦平均(FedAvg)。FedAvg 通过计算模型间的参数算术平均值来在本地模型集上执行,生成一个聚合模型。重要的是要理解,仅进行一次聚合不足以产生一个好的全局聚合模型;相反,是通过在本地训练先前的全局模型并将产生的本地模型聚合到一个新的全局模型中,这个过程允许实现全局训练的进展。
FL 流程
为了从迭代过程的角度更好地理解 FL,我们将它分解为单个迭代或轮的核心组成步骤。
一轮的步骤可以描述如下:
-
将聚合全局模型参数发送到每个用户的设备。
-
用户设备上接收到的 ML 模型使用本地数据进行训练。
-
在一定量的训练之后,本地模型参数被发送到中心服务器。
-
中心服务器通过应用聚合函数来聚合本地模型,生成一个新的聚合全局模型。
这些步骤在图 2.5中有所描述:

图 2.5 – FL 步骤
从步骤 1 到 4的流程构成了一个 FL 的单轮。下一轮开始于用户服务器/设备接收到新创建的聚合模型并开始在本地数据上训练。
让我们回顾一下谷歌为 Gboard 提供的单词推荐功能。在某个时间点,每部手机都会存储足够多的用户打字数据。边缘训练过程可以从这些数据中创建一个本地模型,并将参数发送到中央服务器。在收到一定数量的手机发送的参数后,服务器将它们汇总以创建一个全局模型,并将其发送到手机。这样,连接到服务器的每部手机都会接收到一个反映所有手机本地数据的模型,而无需从它们那里传输数据。反过来,当收集到另一批足够的数据时,每部手机都会重新训练模型,将模型发送到服务器,并接收一个新的全局模型。这个周期会根据 FL 系统的配置反复进行,从而实现全局模型的持续监控和更新。
注意,用户数据从未离开边缘,只有模型参数;也不需要将所有数据放入中央服务器以生成全局模型,这允许数据最小化。此外,可以使用 FL 方法减轻模型偏差,如第第三章中讨论的,联邦学习系统的运作。这就是为什么 FL 可以被视为解决在第一章中引入的大数据三个问题的解决方案,即大数据和传统 AI 的挑战。
迁移学习
FL 与机器学习中的一个概念迁移学习(TL)密切相关。迁移学习允许我们使用研究人员使用大量计算能力和资源在非常通用的数据集上训练的大型深度学习模型。这些模型可以应用于更具体的问题。
例如,我们可以取一个训练用于在图像中定位和命名特定对象的物体检测模型,并在包含我们感兴趣且未包含在原始数据中的特定对象的有限数据集上重新训练它。如果你要从原始数据中取出,添加我们感兴趣的那些对象的资料,然后从头开始训练一个模型,将需要大量的计算时间和能力。有了迁移学习,你可以通过利用现有大型通用模型的一个关键事实来加快这个过程。大型深度神经网络的中层通常非常擅长提取特征,这些特征被后续层用于特定的机器学习任务。我们可以通过保留这些层中的参数来维持其提取特征的学习能力。
换句话说,现有预训练模型某些层的参数可以保留并用于检测新对象——我们不需要重新发明轮子。这种技术被称为参数冻结。在 FL 中,模型训练通常在计算能力有限的本地设备/服务器上进行。一个使用 Gboard 场景的例子是在预训练的词嵌入层上执行参数冻结,以便训练可以专注于特定任务的信息,利用嵌入的先前训练来大大减少可训练参数的数量。
将这个概念进一步扩展,联邦学习(FL)和迁移学习(TL)的交集被称为联邦迁移学习(FTL)。FTL 允许在本地数据集结构不同的场景下应用 FL 方法,通过在模型的一个共享子集上执行 FL,该子集可以后来扩展用于特定任务。例如,一个情感分析模型和一个文本摘要模型可以共享一个句子编码组件,该组件可以使用 FL 进行训练,并用于这两个任务。TL(以及由此扩展的 FTL)是允许在 FL 中实现训练效率和增量改进的关键概念。
个性化
当边缘设备处理的数据不是独立同分布(IID)时,每个设备都可以定制全局模型。这是一个被称为个性化的概念,可以被视为使用本地数据对全局模型进行微调,或者是在数据中战略性地使用偏差。
例如,考虑一个在两个具有不同本地人口统计数据的地区运营的购物中心连锁店(即,该连锁店处理非 IID 数据)。如果该连锁店使用 FL 为两个地点寻求租户推荐,那么每个地点都可以通过个性化的模型比单一的全局模型更好地得到服务,从而有助于吸引当地客户。由于个性化模型是通过本地数据微调或偏差的,我们可以预期它在通用数据上的性能不会像全局模型那样好。另一方面,我们也可以预期个性化模型在为模型个性化设计的本地数据上的性能会优于全局模型。在用户特定性能和泛化性之间存在权衡,而 FL 系统的强大之处在于其灵活性,可以根据需求平衡它们。
水平和垂直 FL
FL 有两种类型:水平或同质FL 和 垂直或异质FL。水平 FL,也称为基于样本的 FL,适用于所有与聚合服务器连接的本地数据集具有相同的特征但包含不同的样本的情况。前面讨论的 Gboard 应用就是一个很好的水平 FL 的例子,即跨设备 FL,也就是说,本地训练发生在边缘设备上。所有安卓手机的数据库格式相同,但内容独特,反映了用户的打字历史。另一方面,垂直 FL,或基于特征的 FL,是一种更先进的技术,允许持有相同样本的不同特征的各方合作生成一个全局模型。
例如,一家银行和一家电子商务公司可能都存储了一个城市的居民数据,但他们的特征会有所不同:前者了解公民的信用和支出模式,后者了解他们的购物行为。他们都可以通过共享有价值的见解而无需共享客户数据来受益。首先,银行和电子商务公司可以使用私有集合交集(PSI)技术来识别他们的共同用户,同时使用Rivest-Shamir-Adleman(RSA)加密来保护数据隐私。接下来,每个当事人使用包含独特特征的本地数据训练一个初步模型。然后,这些模型被聚合起来构建一个全局模型。通常,垂直 FL 涉及多个数据孤岛,在这种情况下,它也被称为跨孤岛 FL。在中国,联邦人工智能生态系统(FATE)因其涉及 WeBank 的垂直 FL 的开创性演示而闻名。如果您对 FL 的进一步概念细节感兴趣,Cloudera Fast Forward Labs 有一个非常说明性和文笔优美的报告,网址为 https://federated.fastforwardlabs.com/。
本节中包含的 FL 信息应该足以理解以下章节,这些章节将进一步深入探讨此处介绍的一些关键概念。本章的最后部分旨在涵盖一些关注 FL 实际应用的辅助概念。
FL 系统考虑因素
本节主要关注 FL 的多方计算方面,包括理论安全措施和完全去中心化方法。本节的目标是让您意识到一些在实际 FL 应用中应考虑的更实际的考虑因素。
FL 系统的安全性
尽管这项技术尚处于起步阶段,但 FL 在一些领域的实验性使用已经出现。具体来说,金融行业的反洗钱(AML)和医疗行业的药物发现与诊断已经看到了有希望的结果,正如 Consilient 和 Owkin 等公司在该领域的概念验证已经成功实施。在 AML 用例中,银行可以相互合作,以高效地识别欺诈交易,而无需共享他们的账户数据;医院可以在保持其患者数据的同时,提高检测健康问题的机器学习模型。
这些解决方案利用了相对简单的横向跨领域 FL 的力量,如理解 FL部分所述,其应用正在扩展到其他领域。例如,Edgify 是一家位于英国的公司,与英特尔和惠普合作,致力于自动化零售店的收银员。在德国慕尼黑,另一家英国公司 Fetch.ai 正在利用其基于 FL 的技术开发智能城市基础设施。很明显,FL 的实际应用正在迅速增长。
尽管 FL 可以通过其隐私设计(模型参数不暴露隐私)和数据最小化(数据不在中央服务器收集)的方法绕过对数据隐私的担忧,正如在第一章“大数据和传统 AI 的挑战”中讨论的那样,但其实施可能存在潜在的障碍;其中一个例子是 FL 项目参与者之间的不信任。考虑一种情况,银行 A和银行 B同意使用 FL 来开发一个协作式反洗钱(AML)解决方案。他们决定采用共同的模型架构,以便每个银行都可以使用自己的数据训练本地模型,并将结果汇总以创建一个全局模型供双方使用。
FL 的简单实现可能允许一个银行使用其本地模型和聚合模型从另一个银行重建本地模型。据此,该银行可能能够从用于训练另一个银行模型的训练数据中提取关键信息。因此,可能会出现关于哪个方应该托管服务器以聚合本地模型的争议。一个可能的解决方案是由第三方托管服务器并负责模型聚合。然而,银行 A如何知道第三方没有与银行 B勾结,反之亦然?进一步来说,将 FL 系统集成到以安全为重点的领域会导致对每个系统组件的安全性和稳定性的新担忧。与不同 FL 系统方法相关的已知安全问题可能会给对抗性攻击带来额外的潜在弱点,这种弱点超过了该方法的益处。
有几种安全措施可以在不强迫参与者相互信任的情况下允许 FL 协作。使用一种名为差分隐私(DP)的统计方法,每个参与者可以向他们的本地模型参数添加随机噪声,以防止从传输的参数中获取训练数据分布或特定元素的信息。通过从具有零均值和相对较低方差(例如高斯、拉普拉斯)的对称分布中采样随机噪声,预期在聚合时添加到本地模型中的随机差异将被抵消。因此,预期全局模型将与没有 DP 生成的模型非常相似。
然而,这种方法存在一个关键的局限性;为了使添加的随机噪声收敛到零,必须足够多的参与者加入联盟。对于仅涉及少数银行或医院的工程项目来说,这种情况可能并不适用,在这种情况下使用差分隐私(DP)会损害全局模型的完整性。可能需要采取一些额外的措施,例如,每个参与者发送他们本地模型的多个副本以增加模型数量,从而使噪声得到抵消。在某些完全去中心化的联邦学习(FL)系统中,另一种可能性是安全多方计算(MPC)。基于 MPC 的聚合允许代理之间相互通信并计算聚合模型,而不涉及可信的第三方服务器,从而保持模型参数的隐私性。
参与者如何确保系统免受外部攻击?同态加密(HE)在加密过程中保留了加法和乘法对数据的影响,允许以加密形式将本地模型聚合到全局模型中。这阻止了模型参数暴露给没有解密密钥的外部人员。然而,HE 在保护参与者之间通信方面的有效性伴随着极高的计算成本:使用 HE 算法处理数据的操作可能比其他方式慢数百万亿倍!
为了缓解这一挑战,可以使用部分同态加密(HE),它仅与加密中的加法或乘法操作之一兼容;因此,它在计算上比完全同态加密轻得多。使用这种方案,联盟中的每个参与者都可以加密并发送他们的本地模型到聚合器,然后聚合器将所有本地模型相加,并将聚合模型发送回参与者,参与者随后解密模型并将参数除以参与者的数量以接收全局模型。
HE 和 DP 都是 FL 在实际应用中的关键技术。对 FL 在现实场景中实施感兴趣的人可以从 IBM 研究学者 Dinesh C. Verma 所著的《Federated AI for Real-World Business Scenarios》一书中学到很多。
去中心化 FL 和区块链
到目前为止讨论的 FL 架构是基于客户端-服务器网络,即边缘设备与中央聚合服务器交换模型。然而,由于之前讨论的 FL 联盟参与者之间的信任问题;然而,建立一个以聚合器作为单独和中央实体的系统可能会出现问题。聚合器的主持人可能难以保持公正和无偏见地对待自己的数据。此外,拥有一个中央服务器不可避免地会导致 FL 系统中的单点故障,这导致系统弹性低。此外,如果聚合器设置在云服务器上,实施这样的 FL 系统将需要一个熟练的 DevOps 工程师,这可能很难找到且成本高昂。
考虑到这些担忧,本书的主要作者 Kiyoshi Nakayama 共同撰写了一篇关于首次使用区块链技术进行完全去中心化 FL 实验的文章(www.kiyoshi-nakayama.com/publications/BAFFLE.pdf)。利用智能合约来协调模型更新和聚合,构建了一个私有以太坊网络以无服务器方式执行 FL。实验结果表明,基于对等网络的去中心化 FL 可以比基于聚合器的集中式 FL 更加高效和可扩展。在惠普和德国研究机构最近进行的一项实验中,确认了去中心化架构的优越性,他们给使用区块链技术的去中心化 FL 起了一个独特的名字:swarm learning。
虽然 FL 领域的研发正在转向去中心化模式,但本书的其余部分假设使用聚合服务器进行集中式架构。这种设计有两个原因。首先,区块链技术仍然是一个新兴技术,AI 和 ML 研究人员并不一定熟悉。引入对等通信方案可能会使主题过于复杂。其次,FL 本身的逻辑独立于网络架构,集中式模型没有问题,可以说明 FL 的工作原理。
摘要
在本章中,我们讨论了由于所有级别的可访问计算能力的增长而带来的两个关键发展。首先,我们探讨了模型的重要性以及它是如何使 ML 在实用应用中显著增长,计算能力的增加允许产生比手动创建的白色盒系统更强的模型,这些模型可以持续产生。我们称之为 FL 的what – ML 是我们试图使用 FL 执行的内容。
然后,我们退后一步,看看边缘设备是如何达到在合理时间内执行复杂计算的阶段,这对于现实世界的应用来说是有意义的,比如我们手机上的文本推荐模型。我们称之为 FL 的哪里——我们想要执行机器学习的环境。
从“什么”和“哪里”,我们得到这两个发展的交集——直接在边缘设备上使用机器学习模型。记住,对于边缘机器学习案例,标准的中心训练方法由于需要集中收集所有数据而严重受限,这阻止了需要高效通信或数据隐私的应用。我们展示了FL如何直接解决这个问题,通过在数据存储的同一位置进行所有训练以产生本地模型。聚合算法将这些本地模型组合成一个全局模型。通过在本地训练和聚合之间迭代切换,FL 允许创建一个模型,该模型实际上已经在所有数据存储中进行过训练,而无需集中收集数据。
我们通过跳出有效聚合背后的理论,审视系统与架构设计方面的考虑,如模型隐私和完全去中心化等方面,来结束本章。阅读完本章后,应该清楚,当前机器学习(ML)、边缘计算以及实际联邦学习(FL)应用的初步增长表明,FL 在不久的将来有望实现显著增长。
在下一章中,我们将从系统层面考察联邦学习的实现。
进一步阅读
想了解更多本章涉及的主题,请参阅以下参考文献:
-
Ruiz, N. W. (2020, 12 月 22 日). 未来是联邦化的. Medium.
-
Contessa, G. (2010). 科学模型与虚构对象. Synthese, 172(2): 215–229.
-
Frigg, R. 和 Hartmann, S. (2020). 科学中的模型. 在 Zalta, E. N. (编). 斯坦福哲学百科全书.
-
Bunge, M. (1963). 通用黑盒理论. 科学哲学, 30(4): 346-358.
-
Moore, S. K.,Schneider, D. 和 Strickland, E. (2021, 9 月 28 日). 深度学习是如何工作的:驱动今天人工智能的神经网络内部. IEEE Spectrum.
-
Raschka, S. 和 Mirjalili, V. (2019). Python 机器学习:使用 Python、scikit-learn 和 TensorFlow 2 进行机器学习和深度学习(第 3 版). 伯明翰:Packt Publishing.
-
McMahan, B., Moore, E., Ramage, D., Hampson, S. 和 Arcas, B. A. (2016). 从去中心化数据中高效学习深度网络. JMLR WandCP, 54.
-
黄,X.,丁,Y.,江,Z. L. 和 Qi,S. (2020). DP-FL:一种用于不平衡数据的创新差分隐私联邦学习框架. World Wide Web, 23: 2529–254.
-
杨,Q.,刘,Y.,陈,T.,和童,Y. (2019). 联邦机器学习:概念与应用. ACM 智能系统与技术交易(TIST),10(2),1-19.
-
Verma, D. C. (2021). 联邦人工智能:现实世界商业场景应用. 佛罗里达:CRC 出版社.
-
Ramanan, P., and Nakayama, K. (2020). Baffle: Blockchain Based Aggregator Free Federated Learning. 2020 IEEE International Conference on Blockchain, 72-81.
-
Warnat-Herresthal, S.,Schultze, H.,Shastry, K. L.,Manamohan, S.,Mukherjee, S.,Garg, V.,和 Schultze, J. L. (2021). 群智学习:用于去中心化和机密临床机器学习. 自然,594(7862),265-270.
第三章:联邦学习系统的工作原理
本章将概述联邦学习系统(FL)的架构、流程流程、消息序列和模型聚合的基本原理。如第二章“什么是联邦学习?”中讨论的,FL 框架的概念基础非常简单且易于理解。然而,FL 框架的真正实现需要具备对 AI 和分布式系统的良好理解。
本章内容基于联邦学习系统最标准的基石,这些基石将在本书后面的实际练习中使用。首先,我们将介绍联邦学习系统的构建块,例如带有联邦学习服务器的聚合器、带有联邦学习客户端的代理、数据库服务器以及这些组件之间的通信。本章介绍的架构以解耦的方式设计,以便对系统的进一步增强将比包含所有内容在一个机器上的联邦学习系统更容易。然后,我们将解释从初始化到聚合的联邦学习操作流程。
最后,我们将探讨如何通过横向设计去中心化的联邦学习设置来扩展联邦学习系统的规模。
本章涵盖了以下主题:
-
联邦学习系统架构
-
理解联邦学习系统流程——从初始化到持续运行
-
模型聚合的基本原理
-
通过横向设计进一步扩展可伸缩性
联邦学习系统架构
联邦学习系统是分散到服务器和分布式客户端的分布式系统。在这里,我们将定义一个具有以下组件的联邦学习系统的代表性架构:带有联邦学习服务器的聚合器、带有联邦学习客户端的代理和数据库:
-
集群聚合器(或聚合器):一个带有联邦学习服务器的系统,它收集和聚合在多个分布式代理(稍后定义)上训练的机器学习模型,并创建全局机器学习模型,这些模型被发送回代理。该系统作为集群聚合器,或者更简单地说,作为联邦学习系统聚合器。
-
分布式代理(或代理):一个带有联邦学习客户端(如本地边缘设备、移动应用、平板电脑或任何分布式云环境)的分布式学习环境,在这些环境中以分布式方式训练机器学习模型并将其发送到聚合器。代理可以通过聚合器的联邦学习客户端通信模块连接到聚合器的联邦学习服务器。联邦学习客户端端的代码包含一系列库,这些库可以集成到由个别机器学习工程师和数据科学家设计和实现的本地机器学习应用中。
-
数据库服务器(或数据库):用于存储与聚合器、代理、全局和本地机器学习模型及其性能指标相关的数据的数据库及其服务器。数据库服务器处理来自聚合器的查询,并将必要的数据发送回聚合器。为了简化联邦学习系统设计,代理不需要直接连接到数据库服务器。
图 3.1展示了典型的整体架构,包括单个集群聚合器和数据库服务器,以及多个分布式代理:

图 3.1 – 联邦学习系统的整体架构
联邦学习系统架构的一个优点是用户不需要将私有原始数据发送到服务器,尤其是第三方拥有的数据。相反,他们只需将本地训练的模型发送给聚合器。本地训练的模型可以有多种格式,例如整个机器学习模型的权重、权重的变化(梯度),甚至它们的子集。另一个优点是减少通信负载,因为用户只需交换通常比原始数据轻得多的模型。
聚类聚合器
集群聚合器由联邦学习服务器模块、联邦学习状态管理模块和模型聚合模块组成,如图 3.1所示。我们只称带有联邦学习服务器的集群聚合器为聚合器。虽然这些模块是聚合器的基础,但可以添加高级模块以确保进一步的安全性和灵活性。由于本书的主要目的是理解联邦学习系统的基本结构和系统流程,因此本书提供的simple-fl GitHub 仓库中未实现一些高级模块。在聚合器系统中,与联邦学习服务器、联邦学习状态管理和模型聚合相关的以下模块是实现聚合器端功能的关键。
-
联邦学习服务器模块:联邦学习服务器模块有三个主要功能,包括通信处理程序、系统配置处理程序和模型合成例程:
-
通信处理器:作为聚合器的一个模块,支持与代理和数据库的通信。通常,此模块接受来自代理的轮询消息并向它们发送响应。他们接收的消息类型包括使用安全凭证和认证机制进行代理注册、初始化作为未来聚合过程初始模型的 ML 模型、确认代理是否参与某一轮次以及重新训练于分布式代理(如移动设备和本地边缘机器)的本地 ML 模型。通信处理器还可以查询数据库服务器,以访问系统数据以及数据库中的 ML 模型,并在聚合器接收或创建新模型后推送和存储这些数据和模型。此模块可以使用 HTTP、WebSocket 或任何其他通信框架来实现其实现。
-
系统配置处理器:处理代理的注册和跟踪连接的代理及其状态。聚合器需要了解代理的连接和注册状态。如果代理使用已建立的认证机制进行注册,它们将接受消息并相应地处理它们。否则,此模块将执行认证过程,例如验证从代理发送的令牌,以便下次此代理连接到 FL 服务器时,系统能够正确识别该代理。
-
模型合成例程:支持检查本地 ML 模型的收集状态,并在满足收集标准后进行聚合。收集标准包括连接的代理收集的本地模型数量。例如,当 80%的连接代理将训练好的本地模型发送到聚合器时,就会发生聚合。实现这一目标的设计模式之一是定期检查代理上传的 ML 模型数量,这些操作在 FL 服务器运行时持续进行。模型合成例程将定期访问数据库或本地缓冲区,以检查本地模型收集的状态并聚合这些模型,以生成将存储在数据库服务器中并发送回代理的全局模型。
-
-
FL 状态管理器:状态管理器跟踪聚合器和连接的代理的状态信息。它存储聚合器的易失性信息,例如代理提供的本地和全局模型、从数据库拉取的集群模型、FL 轮次信息或连接到聚合器的代理。缓冲的本地模型由模型聚合模块使用,以生成发送回连接到聚合器的每个活动代理的全局模型。
-
模型聚合模块:模型聚合模块是本章“模型聚合基础”部分和第七章“模型聚合”中介绍的模型聚合算法的集合。最典型的聚合算法是联邦平均,它平均收集到的 ML 模型的权重,考虑到每个模型用于其本地训练的样本数量。
分布式代理
分布式代理由一个 FL 客户端模块组成,该模块包括通信处理程序和客户端库,以及通过 FL 客户端库连接到 FL 系统的本地 ML 应用程序:
-
FL 客户端模块:FL 客户端模块主要有四个关键功能,包括通信处理程序、代理参与处理程序、模型交换例程和客户端库:
-
通信处理程序:作为与分配给代理的聚合器通信的通道。发送给聚合器的消息包括代理本身的注册有效载荷和一个将成为聚合模型基础的初始模型。该消息还包含本地训练的模型及其性能数据。此模块支持推送和轮询机制,并可以利用 HTTP 或 WebSocket 框架来实现其实现。
-
FL 参与处理程序:通过向聚合器发送包含要注册在 FL 平台上的代理信息本身的消息来处理代理在 FL 过程和周期中的参与。响应消息将设置代理以进行持续和持续的 FL 过程,并且通常包括代理可以利用和本地训练的最新全局模型。
-
模型交换例程:支持一个同步功能,该功能不断检查是否有新的全局模型可用。如果新的全局模型可用,此模块将从聚合器下载全局模型,并在需要时用全局模型替换本地模型。此模块还会检查客户端状态,并在本地训练过程完成后发送重新训练的模型。
-
客户端库:包括管理库和通用 FL 客户端库:
-
当注册其他代理将使用的初始模型时使用管理库。也可以由具有更高控制能力的行政代理请求 FL 系统的任何配置更改。
-
通用 FL 客户端库提供基本功能,例如启动 FL 客户端核心线程、将本地模型发送到聚合器、在本地机器的特定位置保存模型、操作客户端状态以及下载全局模型。本书主要讨论这种通用类型的库。
-
-
-
本地 ML 引擎和数据管道:这些部分由个别 ML 工程师和科学家设计,可以独立于 FL 客户端功能。此模块本身有一个 ML 模型,用户可以立即使用它进行更准确的推理,一个可以插入 FL 客户端库的培训和测试环境,以及数据管道的实现。虽然上述模块和库可以通用并提供为任何 ML 应用程序的应用程序编程接口(API)或库,但此模块根据要开发的 AI 应用程序的需求是独特的。
数据库服务器
数据库服务器由数据库查询处理器和数据库组成,作为存储。数据库服务器可以位于服务器端,例如在云上,并且与聚合器紧密相连,而推荐的设计是将此数据库服务器与聚合器服务器分开,以解耦功能,增强系统的简单性和弹性。数据库查询处理器和示例数据库表的功能如下:
-
数据库查询处理器:接受来自聚合器的传入请求,并将所需数据和 ML 模型发送回聚合器。
-
数据库:存储所有与 FL 过程相关的信息。我们在此列出数据库的一些潜在条目:
-
聚合器信息:此聚合器相关信息包括聚合器本身的 ID、IP 地址和各种端口号、系统注册和更新时间以及系统状态。此外,此条目还可以包括模型聚合相关信息,例如 FL 的轮次及其信息以及聚合标准。
-
代理信息:此代理相关信息包括代理本身的 ID、IP 地址和各种端口号、系统注册和更新时间以及系统状态。此条目还可以包含用于同步 FL(在本章的同步和异步 FL部分中解释)的 opt-in/out 状态,以及一个标志来记录代理是否在过去有过不良行为(例如,参与投毒攻击,或返回结果非常慢)。
-
基础模型信息:基础模型信息用于注册初始 ML 模型,其架构和信息用于 FL 轮次的整个流程。
-
本地模型:本地模型的信息包括唯一标识个别 ML 模型的模型 ID、模型的生成时间、上传模型的代理 ID、从代理接收模型的聚合器 ID 等。通常,模型 ID 唯一映射到实际 ML 模型文件的存储位置,这些文件可以存储在数据库服务器或某些云存储服务中,例如亚马逊网络服务的 S3 存储桶等。
-
集群全局模型:集群全局模型的信息类似于本地模型可以在数据库中记录的信息,包括模型 ID、聚合器 ID、模型的生成时间等。一旦聚合器创建了一个聚合模型,数据库服务器将接受全局模型并将它们存储在数据库服务器或任何云存储服务中。任何全局模型都可以由聚合器请求。
-
性能数据:本地和全局模型的表现可以追踪,作为附加到这些模型的元数据。这些性能数据将被用于确保在将聚合模型实际部署到用户 ML 应用之前,聚合模型的表现足够好。
-
注意
在simple-fl存储库的代码示例中,仅覆盖了与本地模型和集群模型相关的数据库表,以简化整个 FL 过程的解释。
现在 FL 系统的基本架构已经介绍完毕,接下来我们将讨论如果代理端设备计算资源有限时,如何增强 FL 系统的架构。
中间服务器用于低计算能力的代理设备
有时,本地用户设备的计算能力有限 – 在这些设备上可能难以进行 ML 训练,但仅通过下载全局模型就可以进行推理或预测。在这些情况下,FL 平台可能能够设置一个额外的中间服务器层,例如使用智能手机、平板电脑或边缘服务器。例如,在医疗 AI 应用中,用户可以在他们的智能手表上管理他们的健康信息,这些信息可以传输到他们的智能平板电脑或与笔记本电脑同步。在这些设备上,重新训练 ML 模型和集成分布式代理功能都很简单。
因此,系统架构需要根据 FL 系统集成的应用进行修改或重新设计,并且可以使用分布式代理应用中间服务器概念来实现 FL 过程。我们不需要修改聚合器与中间服务器之间的交互和通信机制。只需在用户设备和中间服务器之间实现 API,大多数情况下就可以实现 FL。
图 3.2 展示了聚合器、中间服务器和用户设备之间的交互:


图 3.2 – 带有中间服务器的 FL 系统
现在我们已经了解了 FL 系统的基本架构和组件,接下来我们将探讨在下一节中 FL 系统是如何运行的。
理解 FL 系统流程 – 从初始化到持续运行
每个分布式代理都属于一个由 FL 服务器管理的聚合器,在那里进行 ML 模型聚合,以合成一个将要发送回代理的全局模型。这个概念听起来很简单,因此我们将更详细地探讨这些过程的整个流程。
我们还定义了一个集群全局模型,我们简单地称之为集群模型或全局模型,它是从分布式代理收集的本地模型的聚合 ML 模型。
注意
在接下来的两个章节中,我们将指导您如何实现本章讨论的程序和消息序列。然而,一些系统操作视角,如聚合器或代理在数据库中的系统注册,在simple-fl存储库的代码示例中没有介绍,以简化整个 FL 过程的解释。
数据库、聚合器和代理的初始化
初始化过程的顺序相当简单。初始化和注册过程需要按照数据库、聚合器和代理的顺序进行。聚合器和代理在数据库中的整体注册顺序如图 3.3 所示:


图 3.3 – 数据库服务器中聚合器和代理注册的过程
这里是 FL 系统中每个组件的初始化和注册过程:
-
数据库服务器初始化:FL 系统操作的第一步是启动数据库服务器。多个组织提供了一些简单的框架,这些框架不包括数据库或数据库服务器。然而,为了维护联邦 ML 模型的过程,建议您使用数据库,即使是一个轻量级的数据库,如 SQLite 数据库。
-
聚合器初始化和注册:在代理开始运行并上传机器学习模型之前,应该设置并运行聚合器。当聚合器开始运行并首次连接到数据库服务器时,注册过程会自动进行,同时也会检查聚合器是否安全连接。如果注册过程失败,它会收到数据库发送回来的注册失败消息。此外,如果聚合器在失去与数据库的连接后再次尝试连接到数据库,数据库服务器会检查聚合器是否已经注册。如果是这种情况,数据库服务器响应中会包含已注册聚合器的系统信息,以便聚合器可以从它停止的地方开始。如果聚合器使用 HTTP 或 WebSocket,它可能需要发布 IP 地址和端口号以便代理连接。
-
代理初始化和注册:通常情况下,如果代理知道它想要连接的聚合器,注册过程类似于聚合器连接到数据库服务器的方式。连接过程应该足够简单,只需使用 IP 地址、聚合器的端口号(如果我们使用某些框架,如 HTTP 或 WebSocket)以及一个认证令牌向该聚合器发送一个参与消息。如果代理在失去与聚合器的连接后再次尝试连接到聚合器,数据库服务器会检查代理是否已经注册。如果代理已经注册,数据库服务器响应中会包含已注册代理的系统信息,以便代理可以从与聚合器断开连接的点开始。
特别地,当它收到代理的参与消息时,聚合器会按照图 3.4中的流程进行操作。接收参与请求后的关键过程是(i)检查代理是否可信,或者代理是否已经注册,以及(ii)检查初始全局模型是否已经注册。如果(i)成立,注册过程将继续。如果(初始)全局模型已经注册,代理将能够接收全局模型并开始使用该全局模型在代理端进行本地训练过程。
在聚合器端,代理的参与和注册过程如图 3.4所示:
![图 3.4 – 代理通过聚合器的注册过程
![img/B18369_03_04_New.jpg]
图 3.4 – 代理通过聚合器的注册过程
现在我们已经了解了 FL 系统组件的初始化和注册过程,让我们继续到正在进行的 FL 过程的基本配置,这涉及到上传初始机器学习模型。
初始代理的初始模型上传过程
运行 FL 过程的下一步是注册初始机器学习模型,该模型的架构将被 FL 的所有聚合器和代理在整个连续过程中使用。初始模型可以由拥有机器学习应用程序和 FL 服务器的公司分发。他们可能会将初始基模型作为聚合器配置的一部分提供。
我们将用作模型聚合参考的初始机器学习模型称为基模型。我们还将上传初始基模型的代理称为初始代理。基模型信息可能包括机器学习模型本身以及生成时间和初始性能数据。因此,初始化基模型的过程可以在图 3.5中看到:

](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/fdr-lrn-py/img/B18369_03_05.jpg)
图 3.5 – 初始代理的基模型上传过程
现在,FL 过程已准备好进行。接下来,我们将了解 FL 周期,这是 FL 过程的核心部分。
FL 系统的整体循环和过程
在本节中,我们只提供一个单代理和聚合器的示例,但在实际案例和操作中,代理环境是多样的,分散到分布式设备中。以下是如何上传、聚合、存储本地模型并将其作为全局模型发送回代理的流程列表:
-
除了初始代理之外的其他代理将请求全局模型,这是一个更新的聚合机器学习模型,以便将其部署到自己的应用程序中。
-
一旦代理从聚合器获取更新后的模型并部署它,代理将使用随后获得的新数据在本地重新训练机器学习模型,以反映数据的最新性和时效性。代理还可以参与多个轮次,使用不同的数据来吸收其本地示例和倾向。再次强调,这些本地数据不会与聚合器共享,并保持在分布式设备上本地。
-
在重新训练本地机器学习模型(当然,该模型与 FL 的全局或基模型具有相同的架构)之后,代理调用 FL 客户端 API 将模型发送到聚合器。
-
聚合器接收本地机器学习模型并将模型推送到数据库。聚合器跟踪收集到的本地模型数量,只要联盟轮次开放,就会继续接受本地模型。轮次可以通过任何定义的标准关闭,例如聚合器接收足够多的机器学习模型以进行聚合。当满足标准时,聚合器将聚合本地模型并生成一个更新的全局模型,该模型已准备好发送回代理。
-
在此过程中,代理会不断查询聚合器,以确定全局模型是否已准备好,或者在某些情况下,根据通信系统设计和网络约束,聚合器可能会将全局模型推送到连接到聚合器的代理。然后,更新的模型被发送回代理。
-
在收到更新的全局模型后,代理在准备好时在本地部署和重新训练全局模型。描述的整个过程会重复进行,直到满足联邦学习的终止条件。在某些情况下,没有终止条件来停止这个联邦学习循环和重新训练过程,以便全局模型持续学习最新的现象、当前趋势或与用户相关的倾向。联邦学习回合可以通过手动停止来为一些评估做准备,在部署之前。
图 3.6 展示了联邦学习在代理、聚合器和数据库之间持续进行的过程的总体情况:

图 3.6 – 联邦学习连续循环概述
现在我们已经了解了联邦学习过程的整体流程,我们将探讨联邦学习过程中的不同回合管理方法:同步联邦学习和异步联邦学习。
同步和异步联邦学习
当模型聚合发生在聚合器时,存在多个与从哪些代理收集多少本地模型相关的标准。在本节中,我们将简要讨论同步和异步联邦学习之间的差异,这些差异已在许多文献中讨论过,例如 https://iqua.ece.toronto.edu/papers/ningxinsu-iwqos22.pdf,因此请参阅以进一步了解这些概念。
同步联邦学习
同步联邦学习要求聚合器在每个回合中选择需要发送本地模型的代理,以便进行模型聚合。这种同步联邦学习方法设计简单,易于实现,适用于需要明确选择代理的联邦学习应用。然而,如果代理的数量变得太多,聚合器可能需要等待很长时间才能完成当前回合,因为代理的计算能力可能不同,其中一些可能存在上传问题或未能上传其本地模型。因此,一些代理在向聚合器发送模型时可能会变得缓慢或完全无法工作。这些缓慢的代理在分布式机器学习中被称为 拖沓者,这促使我们使用异步联邦学习模式。
异步联邦学习
异步 FL 不需要聚合器选择必须上传其本地模型的代理。相反,它为任何受信任的代理在任何时候上传模型打开了大门。此外,聚合器想要生成全局模型时,无论是否有收集所需的最小数量本地模型的标准,或者聚合器需要等待接收来自代理的本地模型直到该轮次聚合发生的一些预定义间隔或截止日期,都可以结束联盟轮次。这种异步 FL 方法为每个 FL 轮次的模型聚合提供了 FL 系统更多的灵活性,但设计可能比简单的同步聚合框架更复杂。
在管理 FL 轮次时,需要考虑运行轮次的实际操作,例如调度和处理延迟响应,所需的最小参与水平,示例存储的细节,使用下载或训练的模型在边缘设备上的应用中进行改进推理,以及处理不良或缓慢的代理。
我们将探讨 FL 过程和流程,重点关注聚合器端。
聚合器端的 FL 周期和流程
聚合器运行两个线程来接受和缓存本地模型,并聚合收集到的本地机器学习模型。在本节中,我们将描述这些过程。
接受和缓存本地机器学习模型
聚合器端接受和缓存本地机器学习模型的流程在图 3.7中展示,并如下解释:
-
聚合器将等待代理上传本地机器学习模型。这种方法听起来像是异步 FL。然而,如果聚合器已经决定接受哪些代理上传模型,它只需排除来自不受欢迎的代理的模型上传即可。其他系统或模块可能已经告知不受欢迎的代理不要参与这一轮。
-
一旦收到机器学习模型,聚合器会检查该模型是否由受信任的代理上传。此外,如果上传本地模型的代理不在 FL 操作员希望接受的代理列表中,聚合器将丢弃该模型。此外,聚合器需要有一种机制来仅过滤有效模型——否则,存在毒害全局模型并搞乱整个 FL 过程的危险。
-
如果上传的本地机器学习模型有效,聚合器将把模型推送到数据库。如果数据库位于不同的服务器上,聚合器将打包模型并发送到数据库服务器。
-
当上传的模型存储在数据库中时,它们应在聚合器状态管理器的内存中以适当的格式(如 NumPy 数组)进行缓冲。
此流程会一直运行,直到满足终止条件或 FL 系统的操作员选择停止该过程。图 3.7 描述了接受和缓存本地机器学习模型的流程:

图 3.7 – 接受和缓存本地机器学习模型的流程
一旦本地机器学习模型被接受并缓存,FL 系统继续进行下一个流程:聚合本地模型。
聚合本地机器学习模型
如 图 3.8 所示的聚合器端聚合本地机器学习模型的流程如下:
-
聚合器持续检查聚合标准是否满足。典型的聚合标准如下:
-
在此 FL 轮次中迄今为止收集到的本地模型数量。例如,如果代理的数量是 10 个节点,在 8 个节点(意味着 80%的节点)报告了本地训练的模型后,聚合器开始聚合模型。
-
收集到的模型数量和 FL 轮次所花费的时间的组合。这可以自动化聚合过程,防止系统陷入停滞。
-
-
一旦满足聚合标准,聚合器开始模型聚合过程。通常,联邦平均是一种非常典型但强大的聚合方法。关于模型聚合方法的进一步解释见本章的 模型聚合基础 部分,以及 第七章,模型聚合。在本 FL 轮次中,聚合的模型被定义为全局模型。
在 FL 轮次的时间已过期且参与轮次的代理中上传的模型不足的情况下,该轮次可以被放弃或强制对迄今为止收集到的本地模型进行聚合。
-
一旦模型聚合完成,聚合器将聚合的全局模型推送到数据库。如果数据库位于不同的服务器上,聚合器将打包全局模型并将其发送到数据库服务器。
-
然后,聚合器将全局模型发送给所有代理,或者当代理轮询检查全局模型是否准备就绪时,聚合器将通知代理全局模型已准备就绪,并将其放入对代理的响应消息中。
-
在模型聚合的整个过程中,聚合器仅通过递增来更新 FL 轮次的数量。
图 3.8 显示了在收集到足够的模型后,聚合器从检查聚合标准到合成全局模型的流程:

图 3.8 – 模型合成流程:聚合本地机器学习模型
已经解释了如何聚合本地模型以生成全局模型。现在,让我们看看代理端的 FL 周期,包括本地机器学习模型的重新训练过程。
代理端本地重训练周期和过程
在分布式智能体中,以下状态转换发生,并且为了 FL 循环的持续运行而重复:
-
在
等待 _gm状态,智能体轮询聚合器以接收与全局模型相关的任何更新。基本上,使用轮询方法定期查询更新的全局模型。然而,在某些特定设置下,聚合器可以将更新的全局模型推送到所有智能体。 -
gm_ready是聚合器形成全局模型并由智能体下载后的状态。模型参数在智能体设备中缓存。智能体用下载的全局模型替换其本地机器学习模型。在完全用下载的模型替换本地模型之前,智能体可以检查全局模型的输出是否足够高效以供本地机器学习引擎使用。如果性能不符合预期,用户可以继续使用本地旧模型,直到接收到具有所需性能的全局模型。 -
接下来,在
训练状态中,智能体可以本地训练模型以最大化其性能。训练好的模型保存在一个本地数据存储中,其中包含训练示例。智能体的 FL 客户端库确认其准备就绪,可以操作可以异步函数访问保护的本地区域模型。 -
在本地模型训练完成后,智能体检查新的全局模型是否已发送给智能体。如果全局模型已到达,则丢弃本地训练的机器学习模型,并返回到
gm_ready状态。 -
在本地训练完成后,智能体进入
发送状态,将更新后的本地模型发送回聚合器,然后,智能体返回到等待 _gm状态。
图 3.9 描述了智能体适应和更新机器学习模型的状态转换:

图 3.9 – 智能体侧状态转换以适应和更新机器学习模型
接下来,我们将讨论基于基线输出的偏差的模型解释,这些基线输出用于异常检测和防止模型退化。
基于基线输出偏差的模型解释
我们还可以通过查看每个本地模型的输出提供解释框架。以下程序可以被认为是确保本地模型始终可用并且可以部署到生产中的方法:
-
获取智能体生成的最新机器学习输出以及一个基线输出,该输出可以是用户准备的一个典型期望输出。基线输出可能包括基于过去窗口或操作员、主题专家或基于规则的算法定义的参考点的平均值输出。
-
计算本地模型输出与基线输出之间的偏差。
-
通过检查偏差是否超过操作员指定的阈值,可以检测到异常或性能下降。如果检测到异常,可以向操作员发送警报,指示故障或机器学习模型处于异常状态。
既然联邦学习的过程已经解释清楚,让我们来看看模型聚合的基本概念,它是联邦学习的关键部分。
模型聚合的基本概念
聚合是联邦学习(FL)中的一个核心概念。实际上,用于聚合模型的策略是联邦学习系统性能的关键理论驱动因素。本节的目的在于介绍在联邦学习系统背景下聚合的高级概念——更深入地讨论高级聚合策略的理论和示例将在第七章 模型聚合中进行。
模型聚合究竟意味着什么?
让我们回顾一下在理解联邦学习系统流程——从初始化到持续运行章节中讨论的聚合器端周期,在过程中分配给某个聚合器的代理完成本地训练并将这些模型传回该聚合器时。任何聚合策略,或任何聚合这些模型的方式,的目标是产生新的模型,这些模型在构成代理收集的所有数据上逐渐提高性能。
需要记住的一个重要观点是,按照定义,联邦学习是一种受限的分布式学习设置版本,其中每个代理收集的本地数据不能被其他代理直接访问。如果这种限制不存在,可以通过从每个代理收集数据并在联合数据集上训练来使模型在所有数据上简单地表现良好;因此,将这种集中训练的模型作为联邦学习方法的靶模型是有意义的。从高层次来看,我们可以将这种不受限制的分布式学习场景视为模型训练前的聚合(在这种情况下,聚合指的是结合每个代理的数据)。由于联邦学习不允许数据被其他代理访问,我们将这种情况视为模型训练后的聚合;在这种情况下,聚合指的是结合每个训练模型从其不同的本地数据集中捕获的智能。总结来说,聚合策略的目标是以一种最终导致泛化模型性能接近相应集中训练模型性能的方式结合模型。
FedAvg – 联邦平均
为了使一些想法更加具体,让我们先看看最著名且最直接的聚合策略之一,即联邦平均(FedAvg)。FedAvg 算法的执行过程如下:设
为来自
个代理的模型的参数,每个代理拥有一个本地数据集大小为
。此外,
是定义为
的总数据集大小。然后,FedAvg 返回以下机器学习模型作为聚合模型:

实际上,我们通过对一组模型进行加权平均来执行 FedAvg,权重与用于训练模型的本地数据集大小成比例。因此,FedAvg 可以应用到的模型类型是那些可以表示为一些参数值集合的模型。深度神经网络是目前这类模型中最引人注目的——大多数分析 FedAvg 性能的结果都是与深度学习模型一起工作的。
真是令人惊讶,这种相对简单的方法竟然能导致最终模型产生泛化。我们可以通过在玩具二维参数空间中观察 FedAvg 的样子,来直观地检验聚合策略的好处:

图 3.10 – 来自两个代理(圆形和方形)和目标模型(黑色 x)的二维参数空间中的本地模型
让我们考虑一个有两个新初始化的模型(圆形和方形点)属于不同代理的情况。前面图中的空间代表模型的参数空间,其中每个玩具模型由两个参数定义。随着模型的训练,这些点将在参数空间中移动——目标是接近参数空间中的局部最优解,通常对应于上述集中训练的模型:

图 3.11 – 没有聚合的本地模型参数变化
每个模型都收敛到各自数据集特定的最优解(来自圆形和方形的两个 x 点),这些最优解不具有泛化能力。因为每个代理只能访问数据的一个子集,所以通过本地训练每个模型所达到的局部最优解将与真实的局部最优解不同;这种差异取决于每个代理的底层数据分布的相似程度。如果模型仅在本地进行训练,那么得到的模型可能无法泛化到所有数据:

图 3.12 – 添加聚合将局部模型参数移动到每个步骤中两个模型的平均值,导致在目标模型上收敛
在每一步应用 FedAvg 允许我们创建一个聚合模型,该模型最终在参数空间中接近真实局部最优。
此示例展示了 FedAvg 产生泛化模型的基本能力。然而,与真实模型(如高度参数化的深度学习模型)一起工作引入了额外的复杂性,这由 FedAvg 处理,但不是更简单的方法。例如,我们可能会想知道为什么我们不是简单地完全训练每个本地模型,只在最后平均;虽然这种方法在这个玩具案例中可以工作,但观察到仅对真实模型进行一次平均会导致所有数据上的性能较差。FedAvg 过程允许在高度维参数空间内以更稳健的方式达到泛化模型。
本节仅旨在概述联邦学习中的聚合;第七章,模型聚合,包含对不同场景中聚合的更详细解释和示例。
我们现在已经理解了 FL 系统如何与基本模型聚合工作的整个过程。在某些应用中,FL 系统可能需要支持大量的代理以实现其可扩展性。下一节将为您提供一些关于如何更平滑地扩展的想法,特别是在去中心化的横向设计中。
进一步通过横向设计提高可扩展性
在本节中,我们将探讨在需要支持大量设备和用户时如何进一步提高可扩展性。
在实际案例中,集中式联邦学习提供了控制、易于维护和部署以及低通信开销。如果代理数量不是很大,坚持集中式联邦学习比去中心化联邦学习更有意义。然而,当参与代理的数量变得相当大时,可能值得考虑使用去中心化 FL 架构的横向扩展。如今自动扩展框架的最新发展,如Kubernetes框架(https://kubernetes.io/),可以很好地与本章讨论的主题相结合,尽管与 Kubernetes 的实际集成和实现超出了本书的范围。
带有半全局模型的横向设计
在某些用例中,需要许多聚合器来聚类一组代理并在这许多聚合器之上创建一个全局模型。谷歌采用集中式方法来实现这一点,正如在论文《迈向大规模联邦学习》中所述,而为管理多个聚合器设置一个集中式节点可能存在一些弹性问题。其想法很简单:定期在某个中央主节点上聚合所有聚类模型。
另一方面,我们可以实现由多个聚合器创建的集群模型的去中心化聚合方式。这种架构基于两个关键思想:
-
在没有主节点的情况下,在单个集群聚合器之间进行的模型聚合
-
半全局模型综合以聚合由其他聚合器生成的集群模型
为了创建半全局模型,去中心化的集群聚合器相互交换它们聚合的集群模型,并近似最优的全局模型。集群聚合器还可以使用数据库定期收集其他集群模型以生成半全局模型。这个框架允许通过综合最新的全局模型来吸收来自分散在许多聚合器上的不同用户集的训练结果,而不需要主节点概念。
基于这种去中心化架构,整个 FL 系统的鲁棒性可以得到增强,因为半全局模型可以在每个集群聚合器独立计算。FL 系统可以进一步扩展,因为每个集群聚合器都负责自己创建自己的半全局模型——不是通过这些聚合器的主节点——因此,去中心化的半全局模型形成具有弹性和移动性。
我们甚至可以将存储上传的本地模型、集群全局模型和半全局模型的数据库解耦。通过将分布式数据库引入 FL 系统,整个系统可以变得更加可扩展、弹性,并且安全,同时还有一些故障转移机制。
例如,每个集群聚合器将集群模型存储在分布式数据库中。集群聚合器可以通过定期从数据库中拉取模型来检索其他聚合器的集群模型。在每个集群聚合器,通过综合拉取的模型生成一个半全局 ML 模型。
图 3.13 展示了多聚合器 FL 系统去中心化水平设计的整体架构:
![图 3.13 – 具有多个聚合器的去中心化 FL 系统架构(水平设计)]

图 3.13 – 具有多个聚合器的去中心化 FL 系统架构(水平设计)
既然我们已经讨论了如何通过半全局模型概念使用水平设计来增强 FL 系统,接下来,我们将探讨分布式数据库框架以进一步确保可扩展性和弹性。
分布式数据库
此外,可以通过在数据驱动的分布式数据库中存储历史模型数据来提供模型更新的问责制。星际文件系统(IPFS)和区块链是众所周知的分布式数据库,它们确保了全局模型更新的问责制。当一个集群聚合器基于其他集群模型生成半全局模型后,该半全局模型被存储在分布式数据库中。分布式数据库使用唯一标识符管理这些模型的信息。为了保持所有模型的一致性,包括本地、集群和半全局模型,每个机器学习模型都被分配了一个全局唯一的标识符,例如哈希值,这可以通过使用Chord 分布式哈希表(Chord DHT)的概念来实现。Chord DHT 是一个用于互联网应用的可扩展的 P2P 查找协议。
集群聚合器可以在集群模型上存储元数据,例如时间戳和哈希标识符。这为我们提供了对模型合成的进一步问责制,确保集群模型没有被更改。一旦恶意模型可检测,还可以识别出一组发送有害集群模型以破坏半全局模型的聚合器。这些模型可以通过分析集群模型权重的模式或与其他集群模型的偏差来过滤,当差异太大而无法依赖时。
分布式数据库的本质是存储分布式 FL 系统所有易变的状态信息。在发生故障的情况下,FL 系统可以从分布式数据库中恢复。集群聚合器也会根据系统操作员定义的某个间隔交换它们的集群模型。因此,集群模型和聚合器之间的映射表需要与本地、集群和半全局模型上的元信息一起记录在数据库中,例如这些模型的生成时间和训练样本的大小。
在多聚合器场景中的异步代理参与
分布式代理可以在他们想要加入 FL 过程时向可连接的聚合器广播参与消息。参与消息可以包含代理的唯一 ID。然后,集群聚合器之一会返回一个集群聚合器 ID,这可能是基于一个共同哈希函数生成的值,代理应该属于该值。图 3.14展示了如何使用哈希函数将代理分配给特定的集群聚合器:

图 3.14 – 代理加入 FL 系统中一个集群聚合器的序列
在下一节中,我们将探讨如何基于聚合多个集群全局模型来生成半全局模型。
半全局模型合成
在代理被分配到特定的集群聚合器后,代理开始参与 FL 过程。如果它已注册,则请求基础 ML 模型;否则,它需要上传基础模型以开始本地训练。上传本地模型、生成集群和半全局模型的过程将继续,直到代理或聚合器从系统中断开连接。本地和集群模型上传过程、聚合过程以及半全局模型合成和拉取的序列在图 3.15中说明:

图 3.15 – 从上传本地模型到拉取半全局模型合成过程的序列
让我们看看使用代理、聚合器和分布式数据库之间的流程图来查看半全局模型合成。
聚合器从代理那里接收一个本地模型。在接收本地模型时,模型过滤过程将决定是否接受上传的模型。此框架可以使用许多不同的方法实现,例如检查全局和本地模型权重差异的基本方案。如果模型无效,则简单地丢弃本地模型。
然后,通过聚合所有接受的本地模型创建一个集群模型。聚合器将集群模型存储在数据库中,同时检索其他集群聚合器生成的集群模型。然后从这些集群模型中合成一个半全局模型,并将用于分配给集群聚合器的代理。
图 3.16展示了集群聚合器如何使用分布式数据库进行集群和半全局模型合成:

图 3.16 – 半全局模型合成的流程和流程图
聚合器不需要检索每一轮生成的所有集群模型来创建半全局模型。为了合成一个半全局模型,全局模型可以最终基于每个聚合器随机选择的模型子集收敛。采用这种方法,通过在每次更新时妥协创建全局模型的条件,可以增强聚合器的鲁棒性和独立性。此框架还可以解决集中式 FL 系统典型的计算和通信瓶颈。
摘要
在本章中,我们讨论了 FL 系统内的潜在架构、流程流程和消息序列。典型的 FL 系统架构包括一个聚合器、代理和数据库服务器。这三个组件不断相互通信以交换系统信息和 ML 模型,以实现模型聚合。
实现良好的 FL 系统的关键是解耦关键组件并仔细设计它们之间的接口。我们专注于其设计的简单性,以便只需向系统中添加额外的组件即可实现进一步的增强。水平分布式设计也有助于实现可扩展的 FL 系统。
在下一章中,我们将讨论在服务器端实现 FL 的实现细节。由于本章已介绍了功能的一些关键方面,您将能够实现基本系统并使用一些机器学习应用程序顺利运行模拟。
进一步阅读
本章讨论的一些概念可以通过阅读以下论文进一步探索:
-
Keith Bonawitz, Hubert Eichner, Wolfgang Grieskamp, Dzmitry Huba, Alex Ingerman, Vladimir Ivanov, Chloe Kiddon 等. 迈向大规模联邦学习:系统设计. 机器学习与系统会议论文集 1 (2019): 374–388, (
arxiv.org/abs/1902.01046). -
Kairouz, P., McMahan, H. B., Avent, B., Bellet, A., Bennis, M., Bhagoji, A. N.,以及 Zhao, S. (2021). 联邦学习中的进展和开放问题. 机器学习基础与趋势, 14 (1 和 2): 1–210, (
arxiv.org/abs/1912.04977). -
Stoica, I., Morris, R., Liben-Nowell, D., Karger, D., Kaashoek, M., Dabek, F.,以及 Balakrishnan, H. (2003). Chord: 一个用于互联网应用的可扩展对等查找协议, IEEE/ACM Transactions on Networking., 第 11 卷,第 1 期,第 17–32 页,(
resources.mpi-inf.mpg.de/d5/teaching/ws03_04/p2p-data/11-18-writeup1.pdf). -
Juan Benet. (2014). IPFS – 基于内容寻址、版本化、P2P 文件系统, (
arxiv.org/abs/1407.3561).
第二部分 联邦学习系统的设计与实现
在本部分,我们将使用 Python 解释联邦学习(FL)系统的实现原理。您将学习如何设计软件组件并编写 FL 服务器和客户端的基本功能代码。此外,您还能够将您自己的机器学习过程集成到 FL 系统中,并运行和分析基于 FL 的应用程序。
本部分包括以下章节:
-
第四章, 使用 Python 实现联邦学习服务器
-
第五章, 联邦学习客户端实现
-
第六章, 运行联邦学习系统并分析结果
-
第七章, 模型聚合
第四章:使用 Python 实现联邦学习服务器
联邦学习(federated learning,FL)系统的服务器端实现对于实现真正的 FL 启用应用至关重要。我们在前一章中讨论了基本系统架构和流程。在本章中,我们将讨论更多实际实现,以便您可以创建一个简单的 FL 系统服务器和聚合器,各种机器学习(ML)应用可以连接并在此测试。
本章描述了在第三章“联邦学习系统的工作原理”中讨论的 FL 服务器端组件的实际实现方面。基于对 FL 系统整个工作流程的理解,您将能够进一步使用这里和 GitHub 上提供的示例代码来实现。一旦您理解了使用示例代码的基本实现原则,根据您自己的设计增强 FL 服务器功能将是一个有趣的部分。
在本章中,我们将涵盖以下主题:
-
聚合器的主要软件组件
-
实现 FL 服务器端功能
-
使用状态管理维护聚合模型
-
聚合本地模型
-
运行 FL 服务器
-
实现和运行数据库服务器
-
FL 服务器的潜在增强
技术要求
本章中介绍的所有代码文件都可以在 GitHub 上找到:github.com/tie-set/simple-fl。
重要提示
您可以使用代码文件用于个人或教育目的。然而,请注意,我们不会支持商业部署,并且不会对使用代码造成的任何错误、问题或损害负责。
聚合器和数据库的主要软件组件
在前一章中介绍了具有 FL 服务器的聚合器架构。在这里,我们将介绍实现 FL 系统基本功能的代码。聚合器和数据库端的 Python 软件组件列在fl_main的aggregator目录中,以及lib/util和pseudodb文件夹中,如图 4.1所示:
![图 4.1 – 聚合器的 Python 软件组件以及内部库和伪数据库]
](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/fdr-lrn-py/img/B18369_04_01.jpg)
图 4.1 – 聚合器的 Python 软件组件以及内部库和伪数据库
以下是对聚合器中 Python 代码文件的简要描述。
聚合器端代码
在本节中,我们将涉及与 FL 服务器线程、FL 状态管理以及模型聚合本身相关的聚合器端的主要 Python 文件。这些聚合器端的代码文件位于aggregator文件夹中。仓库中的代码仅捕获模型聚合的视角,而不是创建全面 FL 平台的整个工程方面。
FL 服务器代码(server_th.py)
这是实现联邦学习过程整个基本流程的主要代码,包括聚合器本身、代理和数据库之间的通信过程,以及协调代理参与和 ML 模型的聚合。它还初始化从第一个连接的代理发送的全球集群模型。它管理接收本地模型和集群模型合成例程,在收集足够的本地模型后形成集群全局模型。
联邦学习状态管理器(state_manager.py)
状态管理器缓冲本地模型和集群模型数据,这些数据对于聚合过程是必需的。当聚合器从代理接收本地模型时,缓冲区将被填充,在进入联邦学习过程的下一轮时将被清除。聚合标准检查函数也定义在此文件中。
聚合代码(aggregation.py)
聚合 Python 代码将列出聚合模型的基本算法。在本章中使用的代码示例中,我们只介绍称为联邦平均(FedAvg)的平均方法,它考虑了本地数据集的大小来平均收集的本地模型的权重,从而生成集群全局模型。
lib/util 代码
内部库的 Python 文件(communication_handler.py、data_struc.py、helpers.py、messengers.py和states.py)将在附录、探索内部库中进行解释。
数据库端代码
数据库端代码包括伪数据库和位于pseudodb文件夹中的 SQLite 数据库 Python 代码文件。伪数据库代码运行一个服务器以接收来自聚合器的消息,并将它们处理为可用于联邦学习过程的 ML 模型数据。
伪数据库代码(pseudo_db.py)
伪数据库 Python 代码的功能是接受来自聚合器与本地和全球集群模型相关的消息,并将信息推送到数据库。它还将在本地文件系统中保存 ML 模型二进制文件。
SQLite 数据库代码(sqlite_db.py)
SQLite 数据库 Python 代码在指定的路径创建实际的 SQLite 数据库。它还具有将有关本地和全球集群模型的数据条目插入数据库的功能。
现在聚合器和数据库端软件组件已经定义好了,让我们继续进行聚合器的配置。
面向聚合器的配置
以下代码是聚合器端配置参数的示例,这些参数定义在config_aggregator.json文件中,该文件位于setups文件夹中:
{
"aggr_ip": "localhost",
"db_ip": "localhost",
"reg_socket": "8765",
"exch_socket": "7890",
"recv_socket": "4321",
"db_socket": "9017",
"round_interval": 5,
"aggregation_threshold": 1.0,
"polling": 1
}
参数包括聚合器的 IP(FL 服务器的 IP)、数据库服务器的 IP 以及数据库和代理的各种端口号。轮询间隔是检查聚合标准的时间间隔,聚合阈值定义了开始聚合过程所需的收集的本地 ML 模型的百分比。轮询标志与是否利用polling方法进行聚合器和代理之间的通信有关。
现在我们已经介绍了聚合器侧的配置文件的概念,让我们继续了解代码的设计和实现。
实现 FL 服务器端功能
在本节中,我们将通过实际的代码示例来解释如何使用 FL 服务器系统实现聚合器的第一个版本,这些示例代码位于aggregator目录下的server_th.py文件中。通过这种方式,您将了解 FL 服务器系统的核心功能以及它们的实现方式,以便您能够进一步扩展更多功能。因此,我们只涵盖进行简单 FL 过程所必需的重要和核心功能。潜在的提升将在本章后面的部分列出,即FL 服务器的潜在提升。
server_th.py处理与 FL 服务器端相关的所有基本功能方面,所以让我们在下一节中看看。
导入 FL 服务器的库
FL 服务器端的代码从导入必要的库开始。特别是,lib.util处理使 FL 实现变得容易的基本支持功能。代码的详细信息可以在 GitHub 仓库中找到。
服务器代码导入StateManager和Aggregator用于 FL 过程。关于状态管理器和聚合的代码将在本章后面的部分讨论,即使用状态管理器维护聚合模型和聚合本地模型。
下面是导入必要库的代码:
import asyncio, logging, time, numpy as np
from typing import List, Dict, Any
from fl_main.lib.util.communication_handler import init_fl_server, send, send_websocket, receive
from fl_main.lib.util.data_struc import convert_LDict_to_Dict
from fl_main.lib.util.helpers import read_config, set_config_file
from fl_main.lib.util.messengers import generate_db_push_message, generate_ack_message, generate_cluster_model_dist_message, generate_agent_participation_confirmation_message
from fl_main.lib.util.states import ParticipateMSGLocation, ModelUpMSGLocation, PollingMSGLocation, ModelType, AgentMsgType
from .state_manager import StateManager
from .aggregation import Aggregator
在我们导入必要的库之后,让我们继续设计一个 FL Server类。
定义 FL 服务器类
在实践中,定义Server类是明智的,使用它可以创建一个具有在第三章,联邦学习系统的工作原理中讨论的功能的 FL 服务器实例,如下所示:
class Server:
"""
FL Server class defining the functionalities of
agent registration, global model synthesis, and
handling mechanisms of messages by agents.
"""
再次强调,server类主要提供代理注册和全局模型合成的功能,并处理上传的本地模型和来自代理的轮询消息的机制。它还充当聚合器和数据库以及聚合器和代理之间的接口。
FL 服务器类的功能现在很清晰——接下来是初始化和配置服务器。
初始化 FL 服务器
在 __init__ 构造函数内部的以下代码是 Server 实例初始化过程的示例。
def __init__(self):
config_file = set_config_file("aggregator")
self.config = read_config(config_file)
self.sm = StateManager()
self.agg = Aggregator(self.sm)
self.aggr_ip = self.config['aggr_ip']
self.reg_socket = self.config['reg_socket']
self.recv_socket = self.config['recv_socket']
self.exch_socket = self.config['exch_socket']
self.db_ip = self.config['db_ip']
self.db_socket = self.config['db_socket']
self.round_interval = self.config['round_interval']
self.is_polling = bool(self.config['polling'])
self.sm.agg_threshold =
self.config['aggregation_threshold']
然后,self.config 存储了前面代码块中讨论的 config_aggregator.json 文件中的信息。
self.sm 和 self.agg 分别是下面讨论的状态管理类和聚合类实例。
self.aggr_ip 从聚合器的配置文件中读取一个 IP 地址。
然后,将设置 reg_socket 和 recv_socket,其中 reg_socket 用于代理注册自身以及存储为 self.aggr_ip 的聚合器 IP 地址,而 recv_socket 用于从代理接收本地模型,以及存储为 self.aggr_ip 的聚合器 IP 地址。在这个示例代码中,reg_socket 和 recv_socket 都可以读取自聚合器的配置文件。
exch_socket 是用于将全局模型连同代理的 IP 地址一起发送回代理的端口号,该端口号在初始化过程中通过配置参数进行初始化。
随后,将配置连接到数据库服务器的信息,其中 dp_ip 和 db_socket 分别是数据库服务器的 IP 地址和端口号,所有这些信息都是从 config_aggregator.json 文件中读取的。
round_interval 是一个检查是否满足启动模型聚合过程聚合标准的时间间隔。
is_polling 标志与是否使用代理端的 polling 方法有关。轮询标志必须与代理端配置文件中使用的标志相同。
agg_threshold 也是在 ready_for_local_aggregation 函数中使用的超过收集到的本地模型数量的百分比,如果收集到的模型百分比等于或超过 agg_threshold,则 FL 服务器开始本地模型的聚合过程。
在这个示例代码中,self.round_interval 和 self.agg_threshold 都是从配置文件中读取的。
现在配置已经设置好了,我们将讨论如何注册尝试参与联邦学习(FL)过程的代理。
代理注册函数
在本节中,描述了简化和异步的 register 函数,用于接收指定模型结构的参与消息,并返回用于未来模型交换的套接字信息。它还向代理发送欢迎消息作为响应。
代理的注册过程在下面的示例代码中进行了描述:
async def register(self, websocket: str, path):
msg = await receive(websocket)
es = self._get_exch_socket(msg)
agent_nm = msg[int(ParticipateMSGLocation.agent_name)]
agent_id = msg[int(ParticipateMSGLocation.agent_id)]
ip = msg[int(ParticipateMSGLocation.agent_ip)]
id, es = self.sm.add_agent(agent_nm, agent_id, ip, es)
if self.sm.round == 0:
await self._initialize_fl(msg)
await self._send_updated_global_model( \
websocket, id, es)
在这个示例代码中,从代理接收到的消息,在此定义为 msg,是通过从 communication_handler 代码导入的 receive 函数进行解码的。
特别是,self.sm.add_agent(agent_name, agent_id, addr, es) 函数接收代理名称、代理 ID、代理 IP 地址以及包含在 msg 消息中的 exch_socket 号码,以便接受来自该代理的消息,即使代理暂时断开连接然后再次连接。
之后,注册函数检查是否应该根据 self.sm.round 追踪的 FL 轮次继续到初始化模型的过程。如果 FL 进程尚未开始,即 self.sm.round 为 0,它将调用 _initialize_fl(msg) 函数以初始化 FL 进程。
然后,FL 服务器通过调用 _send_updated_global_model(websocket, id, es) 函数将更新的全局模型发送回代理。该函数接受 WebSocket、代理 ID 和 exch_socket 作为参数,并向代理创建一个回复消息以通知其参与消息是否已被接受。
在此示例代码中,简化了代理与 FL 服务器的注册过程。在生产环境中,所有来自代理的系统信息都将推送到数据库,以便任何失去与 FL 服务器连接的代理都可以通过重新连接到 FL 服务器来随时恢复。
通常情况下,如果 FL 服务器安装在云端,并且代理从其本地环境连接到 FL 服务器,由于安全设置(如防火墙)等原因,聚合器到代理的这种回推机制将不会工作。本书中我们不详细讨论安全问题,因此鼓励您使用 simple-fl 代码中实现的 polling 方法在基于云的聚合器和本地代理之间进行通信。
获取套接字信息以将全局模型推回代理
下面的 _get_exch_socket 函数从代理接收参与消息,并根据消息中的模拟标志决定使用哪个端口来联系代理:
def _get_exch_socket(self, msg):
if msg[int(ParticipateMSGLocation.sim_flag)]:
es = msg[int(ParticipateMSGLocation.exch_socket)]
else:
es = self.exch_socket
return es
在这个实现练习中,我们支持进行模拟运行,通过这种方式,你可以在一台机器上运行数据库、聚合器和多个代理的所有 FL 系统组件。
如有必要,初始化 FL 进程
异步的 _initialize_fl 函数用于初始化一个 FL 进程,该进程仅在 FL 轮次为 0 时被调用。以下是其代码实现:
async def _initialize_fl(self, msg):
agent_id = msg[int(ParticipateMSGLocation.agent_id)]
model_id = msg[int(ParticipateMSGLocation.model_id)]
gene_time = msg[int(ParticipateMSGLocation.gene_time)]
lmodels = msg[int(ParticipateMSGLocation.lmodels)]
perf_val = msg[int(ParticipateMSGLocation.meta_data)]
init_flag = \
bool(msg[int(ParticipateMSGLocation.init_flag)])
self.sm.initialize_model_info(lmodels, init_flag)
await self._push_local_models( \
agent_id, model_id, lmodels, gene_time, perf_val)
self.sm.increment_round()
从接收到的消息中提取代理 ID (agent_id)、模型 ID (model_id)、来自代理的本地模型 (lmodels)、模型的生成时间 (gene_time)、性能数据 (perf_val) 和 init_flag 的值后,调用状态管理器代码中的 initialize_model_info 函数,该函数将在本章后面的部分进行解释。
此函数随后通过调用本节中描述的_push_local_models函数将本地模型推送到数据库。您可以参考将本地和全局模型推送到数据库的函数部分。
然后,轮次增加以进入 FL 的第一轮。
使用更新的全局模型确认代理参与
在初始化(集群)全局模型后,需要通过此注册过程将全局模型发送到连接到聚合器的代理。以下异步的_send_updated_global_model函数处理将全局模型发送到代理的过程,它以 WebSocket 信息、代理 ID 和用于联系代理的端口号作为参数。以下代码块描述了该过程:
async def _send_updated_global_model( \
self, websocket, agent_id, exch_socket):
model_id = self.sm.cluster_model_ids[-1]
cluster_models = \
convert_LDict_to_Dict(self.sm.cluster_models)
reply = generate_agent_participation_confirm_message(
self.sm.id, model_id, cluster_models, self.sm.round,
agent_id, exch_socket, self.recv_socket)
await send_websocket(reply, websocket)
如果 FL 过程已经启动,即self.sm.round已经大于 0,我们将从它们的缓冲区获取集群模型,并使用convert_LDict_to_Dict库函数将它们转换为字典格式。
然后,使用generate_ agent_participation_confirm_message函数包装回复消息,并通过调用send_websocket(reply, websocket)函数将其发送给刚刚连接或重新连接到聚合器的代理。请参阅将全局模型发送到代理的函数部分。
既然我们已经了解了代理的注册过程,让我们继续到处理本地机器学习模型和轮询消息的实现。
处理来自本地代理的消息的服务器
FL 服务器上的异步receive_msg_from_agent过程持续运行,以接收本地模型更新并将它们推送到数据库和内存缓冲区,临时保存本地模型。它还响应来自本地代理的轮询消息。以下代码解释了这一功能:
async def receive_msg_from_agent(self, websocket, path):
msg = await receive(websocket)
if msg[int(ModelUpMSGLocation.msg_type)] == \
AgentMsgType.update:
await self._process_lmodel_upload(msg)
elif msg[int(PollingMSGLocation.msg_type)] == \
AgentMsgType.polling:
await self._process_polling(msg, websocket)
接下来,我们将查看由receive_msg_from_agent函数调用的两个函数,如前述代码块所示,它们是_process_lmodel_upload和_process_polling函数。
处理本地代理的模型上传
异步的_process_lmodel_upload函数处理AgentMsgType.update消息。以下代码块是关于接收本地机器学习模型并将它们放入状态管理器缓冲区的函数:
async def _process_lmodel_upload(self, msg):
lmodels = msg[int(ModelUpMSGLocation.lmodels)]
agent_id = msg[int(ModelUpMSGLocation.agent_id)]
model_id = msg[int(ModelUpMSGLocation.model_id)]
gene_time = msg[int(ModelUpMSGLocation.gene_time)]
perf_val = msg[int(ModelUpMSGLocation.meta_data)]
await self._push_local_models( \
agent_id, model_id, lmodels, gene_time, perf_val)
self.sm.buffer_local_models( \
lmodels, participate=False, meta_data=perf_val)
首先,它从接收到的消息中提取代理 ID(agent_id)、模型 ID(model_id)、来自代理的本地模型(lmodels)、模型的生成时间(gene_time)和性能数据(perf_val),然后调用_push_local_models函数将本地模型推送到数据库。
然后调用buffer_local_models函数以将本地模型(lmodels)保存在内存缓冲区中。buffer_local_models函数在使用状态管理器维护聚合模型部分中描述。
处理代理的轮询
以下异步的_process_polling函数处理AgentMsgType.polling消息:
async def _process_polling(self, msg, websocket):
if self.sm.round > \
int(msg[int(PollingMSGLocation.round)]):
model_id = self.sm.cluster_model_ids[-1]
cluster_models = \
convert_LDict_to_Dict(self.sm.cluster_models)
msg = generate_cluster_model_dist_message( \
self.sm.id, model_id, self.sm.round, \
cluster_models)
await send_websocket(msg, websocket)
else:
msg = generate_ack_message()
await send_websocket(msg, websocket)
如果 FL 轮次(self.sm.round)大于本地 FL 轮次,该轮次包含在本地代理自身维护的接收消息中,这意味着模型聚合是在代理上次向聚合器轮询的时间和现在之间的期间完成的。
在此情况下,通过generate_cluster_model_dist_message函数将cluster_models转换为字典格式后,通过send_websocket函数打包成响应消息并回传给代理。
否则,聚合器将仅通过generate_ack_message函数生成的ACK消息返回给代理。
现在我们已经准备好聚合从代理接收到的本地模型,让我们来看看模型聚合例程。
全局模型合成例程
在 FL 服务器中设计的async def model_synthesis_routine(self)过程通过定期检查存储的模型数量,并在收集到足够多的本地模型以满足聚合阈值时执行全局模型合成。
以下代码描述了模型合成例程过程,该过程定期检查聚合标准并执行模型合成:
async def model_synthesis_routine(self):
while True:
await asyncio.sleep(self.round_interval)
if self.sm.ready_for_local_aggregation():
self.agg.aggregate_local_models()
await self._push_cluster_models()
if self.is_polling == False:
await self._send_cluster_models_to_all()
self.sm.increment_round()
此过程是异步的,使用while循环运行。
特别是,一旦满足ready_for_local_aggregation(在使用状态管理器维护聚合模型部分中解释)设定的条件,就会调用从aggregator.py文件导入的aggregate_local_models函数,该函数基于FedAvg对收集到的本地模型权重进行平均。关于aggregate_local_models函数的进一步解释可以在聚合本地模型部分找到。
然后,调用await self._push_cluster_models()以将聚合的集群全局模型推送到数据库。
await self._send_cluster_models_to_all()用于在未使用polling方法的情况下,将更新的全局模型发送给连接到聚合器的所有代理。
最后但同样重要的是,FL 轮次通过self.sm.increment_round()递增。
一旦生成集群全局模型,就需要使用以下章节中描述的函数将模型发送到连接的代理。
用于将全局模型发送到代理的函数
将全局模型发送到连接的代理的功能由_send_cluster_models_to_all函数处理。这是一个异步函数,用于将集群全局模型发送到本聚合器下的所有代理,如下所示:
async def _send_cluster_models_to_all(self):
model_id = self.sm.cluster_model_ids[-1]
cluster_models = \
convert_LDict_to_Dict(self.sm.cluster_models)
msg = generate_cluster_model_dist_message( \
self.sm.id, model_id, self.sm.round, \
cluster_models)
for agent in self.sm.agent_set:
await send(msg, agent['agent_ip'], agent['socket'])
在获取集群模型信息后,它使用generate_cluster_model_dist_message函数创建包含集群模型、轮次、模型 ID 和聚合器 ID 信息的消息,并调用communication_handler库中的send函数,将全局模型发送到通过代理参与过程注册的agent_set中的所有代理。
已经解释了将集群全局模型发送到连接的代理。接下来,我们将解释如何将本地和集群模型推送到数据库。
将本地和全局模型推送到数据库的函数
_push_local_models和_push_cluster_models函数都内部调用以将本地模型和集群全局模型推送到数据库。
将本地模型推送到数据库
以下是将一组本地模型推送到数据库的_push_local_models函数:
async def _push_local_models(self, agent_id: str, \
model_id: str, local_models: Dict[str, np.array], \
gene_time: float, performance: Dict[str, float]) \
-> List[Any]:
return await self._push_models(
agent_id, ModelType.local, local_models, \
model_id, gene_time, performance)
_push_local_models函数接受诸如代理 ID、本地模型、模型 ID、模型的生成时间以及性能数据等参数,如果有响应消息则返回。
将集群模型推送到数据库
以下是将集群全局模型推送到数据库的_push_cluster_models函数:
async def _push_cluster_models(self) -> List[Any]:
model_id = self.sm.cluster_model_ids[-1]
models = convert_LDict_to_Dict(self.sm.cluster_models)
meta_dict = dict({ \
"num_samples" : self.sm.own_cluster_num_samples})
return await self._push_models( \
self.sm.id, ModelType.cluster, models, model_id, \
time.time(), meta_dict)
在此代码中,_push_cluster_models函数不接收任何参数,因为这些参数可以从状态管理器的实例信息和缓冲内存数据中获取。例如,self.sm.cluster_model_ids[-1]获取最新集群模型的 ID,而self.sm.cluster_models存储最新的集群模型本身,并将其转换为字典格式的models以发送到数据库。它还创建mata_dict来存储样本数量。
将机器学习模型推送到数据库
上述两个函数都按照如下方式调用_push_models函数:
async def _push_models(
self, component_id: str, model_type: ModelType,
models: Dict[str, np.array], model_id: str,
gene_time: float, performance_dict: Dict[str, float])
-> List[Any]:
msg = generate_db_push_message(component_id, \
self.sm.round, model_type, models, model_id, \
gene_time, performance_dict)
resp = await send(msg, self.db_ip, self.db_socket)
return resp
在此代码示例中,_push_models函数接受诸如component_id(聚合器或代理的 ID)、model_type(如本地或集群模型)、models本身、model_id、gene_time(模型创建的时间)以及performance_dict(作为模型的性能指标)等参数。然后,通过generate_db_push_message函数创建要发送到数据库的消息(使用send函数),这些参数包括 FL 轮次信息。它从数据库返回响应消息。
现在我们已经解释了与 FL 服务器相关的所有核心功能,让我们来看看状态管理器的角色,它维护聚合过程所需的全部模型。
使用状态管理器维护聚合所需的模型
在本节中,我们将解释state_manager.py,该文件处理维护模型以及与本地模型聚合相关的必要易失性信息。
导入状态管理器的库
此代码导入了以下内容。data_struc、helpers 和 states 的内部库在 附录、探索内部库 中介绍:
import numpy as np
import logging
import time
from typing import Dict, Any
from fl_main.lib.util.data_struc import LimitedDict
from fl_main.lib.util.helpers import generate_id, generate_model_id
from fl_main.lib.util.states import IDPrefix
在导入必要的库之后,让我们定义状态管理器类。
定义状态管理器类
状态管理器类(Class StateManager),如 state_manager.py 中所见,在以下代码中定义:
class StateManager:
"""
StateManager instance keeps the state of an aggregator.
Functions are listed with this indentation.
"""
这跟踪聚合器的状态信息。聚合器和代理的易变状态也应存储,例如本地模型、连接到聚合器的代理信息、聚合过程生成的聚类模型以及当前轮次编号。
在定义了状态管理器之后,让我们继续初始化状态管理器。
初始化状态管理器
在 __init__ 构造函数中,配置了与联邦学习过程相关的信息。以下代码是构建状态管理器的一个示例:
def __init__(self):
self.id = generate_id()
self.agent_set = list()
self.mnames = list()
self.round = 0
self.local_model_buffers = LimitedDict(self.mnames)
self.local_model_num_samples = list()
self.cluster_models = LimitedDict(self.mnames)
self.cluster_model_ids = list()
self.initialized = False
self.agg_threshold = 1.0
self.id 聚合器的 ID 可以使用来自 util.helpers 库的 generate_id() 函数随机生成。
self.agent_set 是连接到聚合器的代理集合,其中集合的格式是字典信息的集合,在这种情况下与代理相关。
self.mnames 以列表格式存储要聚合的 ML 模型中每一层的名称。
self.round 被初始化为 0 以初始化联邦学习的轮次。
local_model_buffers 是由代理收集并存储在内存空间中的本地模型列表。local_model_buffers 接受来自代理的每个联邦学习轮次的本地模型,一旦聚合过程完成该轮次,此缓冲区将被清除并开始接受下一轮的本地模型。
self.local_model_num_samples 是一个列表,用于存储收集在缓冲区中的模型的样本数量。
self.cluster_models 是以 LimitedDict 格式存储的全局聚类模型集合,而 self.cluster_model_ids 是聚类模型 ID 的列表。
一旦设置了初始全局模型,self.initialized 变为 True,否则为 False。
self.agg_threshold 被初始化为 1.0,该值会被 config_aggregator.json 文件中指定的值覆盖。
在初始化状态管理器之后,让我们接下来调查初始化全局模型。
初始化全局模型
以下 initialize_model_info 函数设置了其他代理将使用的初始全局模型:
def initialize_model_info(self, lmodels, \
init_weights_flag):
for key in lmodels.keys():
self.mnames.append(key)
self.local_model_buffers = LimitedDict(self.mnames)
self.cluster_models = LimitedDict(self.mnames)
self.clear_lmodel_buffers()
if init_weights_flag:
self.initialize_models(lmodels, \
weight_keep=init_weights_flag)
else:
self.initialize_models(lmodels, weight_keep=False)
它填充了从初始代理发送的本地模型(lmodels)中提取的模型名称(self.mnames)。与模型名称一起,local_model_buffers 和 cluster_models 也被重新初始化。在清除本地模型缓冲区后,它调用 initialize_models 函数。
以下 initialize_models 函数根据作为模型参数接收的初始基础模型(以字典格式 str 或 np.array)初始化神经网络的结构(numpy.array):
def initialize_models(self, models: Dict[str, np.array], \
weight_keep: bool = False):
self.clear_saved_models()
for mname in self.mnames:
if weight_keep:
m = models[mname]
else:
m = np.zeros_like(models[mname])
self.cluster_models[mname].append(m)
id = generate_model_id(IDPrefix.aggregator, \
self.id, time.time())
self.cluster_model_ids.append(id)
self.initialized = True
对于模型的每一层,这里定义为模型名称,此函数填写模型参数。根据weight_keep标志,模型以零或接收到的参数初始化。这样,初始的集群全局模型与随机模型 ID 一起构建。如果代理发送的 ML 模型与这里定义的模型架构不同,聚合器将拒绝接受该模型或向代理发送错误信息。不返回任何内容。
因此,我们已经涵盖了全局模型的初始化。在下一节中,我们将解释 FL 过程的主体部分,即检查聚合标准。
检查聚合标准
以下名为ready_for_local_aggregation的代码用于检查聚合标准:
def ready_for_local_aggregation(self) -> bool:
if len(self.mnames) == 0:
return False
num_agents = int(self.agg_threshold * \
len(self.agent_set))
if num_agents == 0: num_agents = 1
num_collected_lmodels = \
len(self.local_model_buffers[self.mnames[0]])
if num_collected_lmodels >= num_agents:
return True
else:
return False
此ready_for_local_aggregation函数返回一个bool值,以标识聚合器是否可以开始聚合过程。如果满足聚合标准(例如收集足够的本地模型以进行聚合),则返回True,否则返回False。聚合阈值agg_threshold在config_aggregator.json文件中配置。
下一节是关于缓存用于聚合过程的本地模型。
缓存本地模型
以下在buffer_local_models上的代码将代理的本地模型存储在本地模型缓冲区中:
def buffer_local_models(self, models: Dict[str, np.array],
participate=False, meta_data: Dict[Any, Any] = {}):
if not participate:
for key, model in models.items():
self.local_model_buffers[key].append(model)
try:
num_samples = meta_data["num_samples"]
except:
num_samples = 1
self.local_model_num_samples.append( \
int(num_samples))
else:
pass
if not self.initialized:
self.initialize_models(models)
参数包括以字典格式表示的本地models以及如样本数量等元信息。
首先,此函数通过检查参与标志来确定从代理发送的本地模型是初始模型还是不是。如果是初始模型,它将调用initialize_model函数,如前述代码块所示。
否则,对于用模型名称定义的模型的每一层,它将numpy数组存储在self.local_model_buffers中。key是模型名称,前述代码中提到的model是模型的实际参数。可选地,它可以接受代理用于重新训练过程的样本数量或数据源,并将其推送到self. local_model_num_samples缓冲区。
当 FL 服务器在receive_msg_from_agent过程中从代理接收本地模型时,会调用此函数。
这样,本地模型缓冲区已经解释完毕。接下来,我们将解释如何清除已保存的模型,以便聚合可以继续进行,而无需在缓冲区中存储不必要的模型。
清除已保存的模型
以下clear_saved_models函数清除本轮存储的所有集群模型:
def clear_saved_models(self):
for mname in self.mnames:
self.cluster_models[mname].clear()
此函数在初始化 FL 过程之初被调用,集群全局模型被清空,以便再次开始新一轮的 FL。
以下函数,clear_lmodel_buffers函数,清除所有缓存的本地模型,为下一轮 FL 做准备:
def clear_lmodel_buffers(self):
for mname in self.mnames:
self.local_model_buffers[mname].clear()
self.local_model_num_samples = list()
在进行下一轮 FL 之前清除local_model_buffers中的本地模型是至关重要的。如果没有这个过程,要聚合的模型将与来自其他轮次的非相关模型混合,最终 FL 的性能有时会下降。
接下来,我们将解释在 FL 过程中添加代理的基本框架。
添加代理
这个add_agent函数处理使用系统内存进行简短的代理注册:
def add_agent(self, agent_name: str, agent_id: str, \
agent_ip: str, socket: str):
for agent in self.agent_set:
if agent_name == agent['agent_name']:
return agent['agent_id'], agent['socket']
agent = {
'agent_name': agent_name,
'agent_id': agent_id,
'agent_ip': agent_ip,
'socket': socket
}
self.agent_set.append(agent)
return agent_id, socket
此函数仅向self.agent_set列表添加与代理相关的信息。代理信息包括代理名称、代理 ID、代理 IP 地址以及用于联系代理的socket编号。socket编号可以在将集群全局模型发送到连接到聚合器的代理时使用,以及在聚合器和代理之间使用push方法进行通信时使用。此函数仅在代理注册过程中调用,并返回代理 ID 和socket编号。
如果代理已经注册,这意味着agent_set中已经存在具有相同名称的代理,它将返回现有代理的代理 ID 和socket编号。
再次强调,从聚合器到代理的此push通信方法在特定安全情况下不起作用。建议使用代理使用的polling方法,以不断检查聚合器是否有更新的全局模型。
可以使用数据库扩展代理注册机制,这将为您提供更好的分布式系统管理。
接下来,我们将涉及 FL 轮次的增加。
增加 FL 轮次
increment_round函数仅精确增加由状态管理器管理的轮次编号:
def increment_round(self):
self.round += 1
增加轮次是 FL 过程中支持连续学习操作的关键部分。此函数仅在注册初始全局模型或每次模型聚合过程之后调用。
现在我们已经了解了 FL 如何与状态管理器协同工作,在接下来的部分,我们将讨论模型聚合框架。
聚合本地模型
aggregation.py代码处理使用一系列聚合算法对本地模型进行聚合。在代码示例中,我们只支持在以下章节中讨论的FedAvg。
导入聚合器的库
aggregation.py代码导入以下内容:
import logging
import time
import numpy as np
from typing import List
from .state_manager import StateManager
from fl_main.lib.util.helpers import generate_model_id
from fl_main.lib.util.states import IDPrefix
在使用状态管理器维护聚合模型部分中讨论了导入的状态管理器的角色和功能,并在附录、探索内部库中介绍了helpers和states库。
在导入必要的库之后,让我们定义聚合器类。
定义和初始化聚合器类
以下class Aggregator的代码定义了聚合器的核心过程,它提供了一套数学函数用于计算聚合模型:
class Aggregator:
"""
Aggregator class instance provides a set of
mathematical functions to compute aggregated models.
"""
以下 __init__ 函数只是设置聚合器的状态管理器以访问模型缓冲区:
def __init__(self, sm: StateManager):
self.sm = sm
一旦聚合器类被定义和初始化,让我们看看实际的 FedAvg 算法实现。
定义 aggregate_local_models 函数
以下 aggregate_local_models 函数是聚合本地模型的代码:
def aggregate_local_models(self):
for mname in self.sm.mnames:
self.sm.cluster_models[mname][0] \
= self._average_aggregate( \
self.sm.local_model_buffers[mname], \
self.sm.local_model_num_samples)
self.sm.own_cluster_num_samples = \
sum(self.sm.local_model_num_samples)
id = generate_model_id( \
IDPrefix.aggregator, self.sm.id, time.time())
self.sm.cluster_model_ids.append(id)
self.sm.clear_lmodel_buffers()
此函数可以在聚合标准满足后调用,例如在 config_aggregator.json 文件中定义的聚合阈值。聚合过程使用状态管理器内存中缓存的本地 ML 模型。这些本地 ML 模型来自注册的代理。对于由 mname 定义的模型的每一层,模型权重由 _average_aggregate 函数如下平均,以实现 FedAvg。在平均所有层的模型参数后,cluster_models 被更新,并发送给所有代理。
然后,清除本地模型缓冲区,为下一轮 FL 流程做好准备。
FedAvg 函数
以下函数 _average_aggregate,由前面的 aggregate_local_models 函数调用,是实现 FedAvg 聚合方法的代码:
def _average_aggregate(self, buffer: List[np.array],
num_samples: List[int]) -> np.array:
denominator = sum(num_samples)
model = float(num_samples[0])/denominator * buffer[0]
for i in range(1, len(buffer)):
model += float(num_samples[i]) /
denominator * buffer[i]
return model
在 _average_aggregate 函数中,计算足够简单,对于给定的 ML 模型列表的每个缓冲区,它为模型取平均参数。模型聚合的基本原理在 第三章,联邦学习系统的工作原理 中讨论。它使用 np.array 返回加权聚合模型。
现在我们已经涵盖了 FL 服务器和聚合器的所有基本功能,接下来,我们将讨论如何运行 FL 服务器本身。
运行 FL 服务器
这里是一个运行 FL 服务器的示例。为了运行 FL 服务器,你只需执行以下代码:
if __name__ == "__main__":
s = Server()
init_fl_server(s.register,
s.receive_msg_from_agent,
s.model_synthesis_routine(),
s.aggr_ip, s.reg_socket, s.recv_socket)
FL 服务器实例的 register、receive_msg_from_agent 和 model_synthesis_routine 函数用于启动代理的注册过程、接收代理的消息以及启动模型合成过程以创建全局模型,所有这些都是使用 communication_handler 库中的 init_fl_server 函数启动的。
我们已经使用 FL 服务器涵盖了聚合器的所有核心模块。它们可以与数据库服务器一起工作,这将在下一节中讨论。
实现和运行数据库服务器
数据库服务器可以托管在聚合器服务器所在的同一台机器上,也可以与聚合器服务器分开。无论数据库服务器是否托管在同一台机器上,这里引入的代码都适用于这两种情况。数据库相关的代码可以在本书提供的 GitHub 仓库的 fl_main/pseudodb 文件夹中找到。
面向数据库的配置
以下代码是作为 config_db.json 保存的数据库端配置参数的示例:
{
"db_ip": "localhost",
"db_socket": "9017",
"db_name": "sample_data",
"db_data_path": "./db",
"db_model_path": "./db/models"
}
特别是,db_data_path是 SQLite 数据库的位置,db_model_path是 ML 模型二进制文件的位置。config_db.json文件可以在setup文件夹中找到。
接下来,让我们定义数据库服务器并导入必要的库。
定义数据库服务器
pseudo_db.py代码的主要功能是接收包含本地和集群全局模型的包含消息。
导入伪数据库所需的库
首先,pseudo_db.py代码导入了以下内容:
import pickle, logging, time, os
from typing import Any, List
from .sqlite_db import SQLiteDBHandler
from fl_main.lib.util.helpers import generate_id, read_config, set_config_file
from fl_main.lib.util.states import DBMsgType, DBPushMsgLocation, ModelType
from fl_main.lib.util.communication_handler import init_db_server, send_websocket, receive
它导入了基本通用库以及SQLiteDBHandler(在使用 SQLite 定义数据库部分中讨论)和来自lib/util库的函数,这些函数在附录、探索内部库中讨论。
定义 PseudoDB 类
然后定义了PseudoDB类以创建一个实例,该实例从聚合器接收模型及其数据并将其推送到实际的数据库(在这个例子中是 SQLite):
class PseudoDB:
"""
PseudoDB class instance receives models and their data
from an aggregator, and pushes them to database
"""
现在,让我们继续初始化PseudoDB的实例。
初始化 PseudoDB
然后,初始化过程__init__被定义为以下内容:
def __init__(self):
self.id = generate_id()
self.config = read_config(set_config_file("db"))
self.db_ip = self.config['db_ip']
self.db_socket = self.config['db_socket']
self.data_path = self.config['db_data_path']
if not os.path.exists(self.data_path):
os.makedirs(self.data_path)
self.db_file = \
f'{self.data_path}/model_data{time.time()}.db'
self.dbhandler = SQLiteDBHandler(self.db_file)
self.dbhandler.initialize_DB()
self.db_model_path = self.config['db_model_path']
if not os.path.exists(self.db_model_path):
os.makedirs(self.db_model_path)
初始化过程生成实例 ID 并设置各种参数,如数据库套接字(db_socket)、数据库 IP 地址(db_ip)、数据库路径(data_path)和数据库文件(db_file),所有这些均从config_db.json配置。
dbhandler存储SQLiteDBHandler的实例并调用initialize_DB函数来创建 SQLite 数据库。
如果不存在,则创建data_path和db_model_path的文件夹。
在PseudoDB的初始化过程之后,我们需要设计通信模块以接收来自聚合器的消息。我们再次使用 WebSocket 与聚合器进行通信,并将此模块作为服务器启动以接收和响应来自聚合器的消息。在这个设计中,我们不将来自数据库服务器的消息推送到聚合器或代理,以简化 FL 机制。
处理来自聚合器的消息
以下是对async def handler函数的代码,该函数以websocket作为参数,接收来自聚合器的消息并返回所需的信息:
async def handler(self, websocket, path):
msg = await receive(websocket)
msg_type = msg[DBPushMsgLocation.msg_type]
reply = list()
if msg_type == DBMsgType.push:
self._push_all_data_to_db(msg)
reply.append('confirmation')
else:
raise TypeError(f'Undefined DB Message Type: \
{msg_type}.')
await send_websocket(reply, websocket)
在handler函数中,一旦它解码了从聚合器接收到的消息,handler函数会检查消息类型是否为push。如果是,它将尝试通过调用_push_all_data_to_db函数将本地或集群模型推送到数据库。否则,它将显示错误消息。然后可以将确认消息发送回聚合器。
在这里,我们只定义了push消息的类型,但你可以定义尽可能多的类型,同时增强数据库模式设计。
将所有数据推送到数据库
以下_push_all_data_to_db代码将模型信息推送到数据库:
def _push_all_data_to_db(self, msg: List[Any]):
pm = self._parse_message(msg)
self.dbhandler.insert_an_entry(*pm)
model_id = msg[int(DBPushMsgLocation.model_id)]
models = msg[int(DBPushMsgLocation.models)]
fname = f'{self.db_model_path}/{model_id}.binaryfile'
with open(fname, 'wb') as f:
pickle.dump(models, f)
模型的信息通过_parse_message函数提取,并传递给_insert_an_entry函数。然后,实际的模型保存在本地服务器文件系统中,其中模型的文件名和路径由这里的db_model_path和fname定义。
解析消息
_parse_message函数仅从接收到的消息中提取参数:
def _parse_message(self, msg: List[Any]):
component_id = msg[int(DBPushMsgLocation.component_id)]
r = msg[int(DBPushMsgLocation.round)]
mt = msg[int(DBPushMsgLocation.model_type)]
model_id = msg[int(DBPushMsgLocation.model_id)]
gene_time = msg[int(DBPushMsgLocation.gene_time)]
meta_data = msg[int(DBPushMsgLocation.meta_data)]
local_prfmc = 0.0
if mt == ModelType.local:
try: local_prfmc = meta_data["accuracy"]
except: pass
num_samples = 0
try: num_samples = meta_data["num_samples"]
except: pass
return component_id, r, mt, model_id, gene_time, \
local_prfmc, num_samples
此函数将接收到的消息解析为与代理 ID 或聚合器 ID(component_id)、轮数(r)、消息类型(mt)、model_id、模型生成时间(gene_time)以及以字典格式(meta_data)的性能数据相关的参数。当模型类型为本地时,提取本地性能数据local_prfmc。从meta_dect中提取在本地设备上使用的样本数据量。所有这些提取的参数在最后返回。
在以下部分,我们将解释使用 SQLite 框架实现的数据库。
使用 SQLite 定义数据库
sqlite_db.py代码创建 SQLite 数据库并处理从数据库中存储和检索数据。
导入 SQLite 数据库的库
sqlite_db.py按照如下方式导入基本通用库和ModelType:
import sqlite3
import datetime
import logging
from fl_main.lib.util.states import ModelType
lib/util中的ModelType定义了模型类型:本地模型和(全局)集群模型。
定义和初始化 SQLiteDBHandler 类
然后,以下与SQLiteDBHandler类相关的代码创建并初始化 SQLite 数据库,并将模型插入 SQLite 数据库:
class SQLiteDBHandler:
"""
SQLiteDB Handler class that creates and initialize
SQLite DB, and inserts models to the SQLiteDB
"""
初始化非常简单——只需将PseudoDB实例传递的db_file参数设置为self.db_file:
def __init__(self, db_file):
self.db_file = db_file
初始化数据库
在下面的initialize_DB函数中,使用 SQLite(sqlite3)定义了本地和集群模型的数据库表:
def initialize_DB(self):
conn = sqlite3.connect(f'{self.db_file}')
c = conn.cursor()
c.execute('''CREATE TABLE local_models(model_id, \
generation_time, agent_id, round, performance, \
num_samples)''')
c.execute('''CREATE TABLE cluster_models(model_id, \
generation_time, aggregator_id, round, \
num_samples)''')
conn.commit()
conn.close()
在本例中,表被简化了,这样你可以轻松地跟踪上传的本地模型及其性能,以及由聚合器创建的全局模型。
local_models表具有模型 ID(model_id)、模型生成时间(generation_time)、上传的本地模型代理 ID(agent_id)、轮次信息(round)、本地模型的性能数据(performance)以及用于 FedAvg 聚合使用的样本数量(num_samples)。
cluster_models具有模型 ID(model_id)、模型生成时间(generation_time)、聚合器 ID(aggregator_id)、轮次信息(round)和样本数量(num_samples)。
将条目插入数据库
以下代码用于insert_an_entry,使用sqlite3库将接收到的参数数据插入:
def insert_an_entry(self, component_id: str, r: int, mt: \
ModelType, model_id: str, gtime: float, local_prfmc: \
float, num_samples: int):
conn = sqlite3.connect(self.db_file)
c = conn.cursor()
t = datetime.datetime.fromtimestamp(gtime)
gene_time = t.strftime('%m/%d/%Y %H:%M:%S')
if mt == ModelType.local:
c.execute('''INSERT INTO local_models VALUES \
(?, ?, ?, ?, ?, ?);''', (model_id, gene_time, \
component_id, r, local_prfmc, num_samples))
elif mt == ModelType.cluster:
c.execute('''INSERT INTO cluster_models VALUES \
(?, ?, ?, ?, ?);''', (model_id, gene_time, \
component_id, r, num_samples))
conn.commit()
conn.close()
此函数接受component_id(代理 ID 或聚合器 ID)、轮数(r)、消息类型(mt)、模型 ID(model_id)、模型生成时间(gtime)、本地模型性能数据(local_prfmc)和要插入的样本数量(num_samples)作为参数,使用 SQLite 库的execute函数插入条目。
如果模型类型是本地,则将模型信息插入到local_models表中。如果模型类型是集群,则将模型信息插入到cluster_models表中。
其他功能,例如更新和删除数据库中的数据,在此示例代码中未实现,您需要自行编写这些附加功能。
在下一节中,我们将解释如何运行数据库服务器。
运行数据库服务器
下面是使用 SQLite 数据库运行数据库服务器的代码:
if __name__ == "__main__":
pdb = PseudoDB()
init_db_server(pdb.handler, pdb.db_ip, pdb.db_socket)
PseudoDB类的实例被创建为pdb。pdb.handler、数据库的 IP 地址(pdb.db_ip)和数据库套接字(pdb.db_socket)用于启动从init_db_server函数中启用并由communication_handler库在util/lib文件夹中提供的聚合器接收本地和集群模型的过程。
现在,我们了解了如何实现和运行数据库服务器。本章中讨论的数据库表和模式设计得尽可能简单,以便我们理解 FL 服务器流程的基本原理。在下一节中,我们将讨论 FL 服务器的潜在增强功能。
FL 服务器的潜在增强功能
在本章中讨论了 FL 服务器的一些关键潜在增强功能。
重新设计数据库
在本书中,数据库有意设计为包含最少的表信息,需要扩展,例如通过在数据库中添加聚合器本身、代理、初始基础模型和项目信息等表。例如,本章中描述的 FL 系统不支持服务器和代理进程的终止和重启。因此,FL 服务器的实现并不完整,因为它在系统停止或失败时丢失了大部分信息。
自动注册初始模型
为了简化注册初始模型的过程的解释,我们使用模型名称定义了 ML 模型的层。在系统中注册此模型可以自动化,这样只需加载具有.pt/.pth和.h5等文件扩展名的特定 ML 模型(如 PyTorch 或 Keras 模型),FL 系统的用户就可以开始这个过程。
本地模型和全局模型性能指标
再次,为了简化对 FL 服务器和数据库端功能的解释,准确度值仅被用作模型性能标准之一。通常,机器学习应用有更多指标需要跟踪作为性能数据,并且它们需要与数据库和通信协议设计一起增强。
微调聚合
为了简化聚合本地模型的过程,我们仅使用了 FedAvg,这是一种加权平均方法。样本数量可以根据本地环境动态变化,这一方面由你增强。还有各种模型聚合方法,这些方法将在本书的第七章“模型聚合”中解释,以便你可以根据要创建和集成到 FL 系统中的 ML 应用选择最佳的聚合方法。
摘要
在本章中,通过实际的代码示例解释了 FL 服务器端实现的基本原理和原则。跟随本章内容后,你现在应该能够使用模型聚合机制构建 FL 服务器端功能。
这里介绍的服务器端组件包括基本的通信、代理和初始模型的注册、用于聚合的状态信息管理,以及创建全局集群模型的聚合机制。此外,我们还讨论了仅存储 ML 模型信息的数据库实现。代码被简化,以便你能够理解服务器端功能的原则。构建更可持续、弹性、可扩展的 FL 系统的许多其他方面的进一步改进取决于你。
在下一章中,我们将讨论实现 FL 客户端和代理功能的原则。客户端需要为机器学习应用提供一些精心设计的 API 以供插件使用。因此,本章将讨论 FL 客户端的核心功能库以及库集成到非常简单的 ML 应用中,以实现整个 FL 过程。
第五章:联邦学习客户端实现
根据系统架构、顺序和流程,如第3 章“联邦学习系统的工作原理”中所述,联邦学习(FL)系统的客户端模块可以基于系统架构、顺序和流程实现。FL 客户端功能可以将进行本地训练和测试的分布式机器学习(ML)应用程序与聚合器通过客户端库中嵌入的通信模块连接起来。
在使用 FL 客户端库的本地 ML 引擎示例中,将讨论最小引擎包示例,使用虚拟 ML 模型来理解与本章设计的 FL 客户端库集成的过程。通过遵循集成示例代码,您将了解如何实际启用与 FL 客户端相关的整个流程,如第3 章“联邦学习系统的工作原理”中所述,同时将在第6 章“运行联邦学习系统并分析结果”中讨论最小示例将发生什么。
在本章中,将讨论在本地 ML 引擎中使用的 FL 客户端功能的设计和实现原理概述。通过阅读本章,您将能够编写 FL 客户端模块和库以及分布式本地 ML 引擎的代码,例如使用卷积神经网络(CNNs)进行图像分类。
在本章中,我们将涵盖以下主题:
-
FL 客户端组件概述
-
实现 FL 客户端主要功能
-
设计 FL 客户端库
-
本地 ML 引擎集成到 FL 系统中
-
将图像分类集成到 FL 系统中的示例
技术要求
本章中介绍的所有代码文件都可以在 GitHub 上找到(github.com/tie-set/simple-fl)。
重要提示
您可以使用代码文件用于个人或教育目的。请注意,我们不会支持商业部署,并且不对使用代码造成的任何错误、问题或损害负责。
FL 客户端组件概述
在第3 章“联邦学习系统的工作原理”中介绍了 FL 客户端作为代理的架构。在此,我们将介绍实现 FL 客户端基本功能的代码。在此处,软件架构的客户端被简化,仅可以使用此示例中的client.py文件,以及来自lib/util文件夹的支持函数,如图5.1所示:
![图 5.1 – 作为代理的 FL 客户端的 Python 软件组件
![img/B18369_05_01.jpg]
图 5.1 – 作为代理的 FL 客户端的 Python 软件组件
以下部分简要描述了 FL 系统代理的 Python 文件。
分布式代理端代码
对于代理端,fl_main/agent目录中有一个主要文件client.py,它处理大多数 FL 客户端功能。
FL 客户端代码(client.py)
agent文件夹中的client.py文件包含参与 FL 周期、与聚合器进行 ML 模型交换的框架以及推送和轮询机制以与聚合器通信的功能。客户端的功能还可以作为本地 ML 应用程序与 FL 系统本身的接口,为 ML 引擎提供 FL 客户端库。这是将本地训练的 ML 模型连接到 FL 服务器和聚合器的主要代码。您需要自己准备一个本地 ML 应用程序,我们将帮助您了解如何使用 FL 客户端库将您的 ML 引擎集成到 FL 系统中,这是本章的另一个主要主题。
lib/util 代码
将对支持 Python 代码(communication_handler.py、data_struc.py、helpers.py、messengers.py和states.py)作为内部库的解释包含在附录,探索内部库中。
代理配置
以下是我们使用的代码中保存为config_agent.json的客户端配置参数示例:
{
"aggr_ip": "localhost",
"reg_socket": "8765",
"model_path": "./data/agents",
"local_model_file_name": "lms.binaryfile",
"global_model_file_name": "gms.binaryfile",
"state_file_name": "state",
"init_weights_flag": 1,
"polling": 1
}
聚合器的 IP(aggr_ip)和端口号(reg_socket)用于连接到 FL 服务器,在那里进行本地模型的聚合。此外,模型路径参数model_path指定了本地模型(命名为local_model_file_name)和全局模型(命名为global_model_file_name)的位置。本地和全局模型存储为二进制文件(本例中为lms.binaryfile和gms.binaryfile)。状态文件(命名为state_file_name)记录客户端的本地状态,定义了等待全局模型、训练模型、发送训练好的模型等。init_weights_flag在系统操作员希望使用某些权重初始化全局模型时使用。如果标志为1,代理将发送预配置的模型;否则,模型将在聚合器端填充为零。轮询标志(polling)涉及是否在代理和聚合器之间使用轮询方法进行通信。
现在我们已经讨论了 FL 客户端模块,让我们来看看实际的实现和一些代码,以实现 FL 客户端的功能。
实现 FL 客户端主要功能
在本节中,我们将解释如何实现基本的联邦学习客户端代码,该代码在 agent 目录下的 client.py 文件中有描述。请参阅第三章,联邦学习系统的工作原理,以了解联邦学习客户端架构、序列和流程。通过学习这段客户端代码,您将了解如何实现智能体的注册过程、模型交换同步以及推送/轮询机制,以及智能体和聚合器之间的通信协议,以及一些将在其他机器学习应用中作为应用程序编程接口(API)调用的函数。
让我们先看看实现联邦学习客户端功能需要导入哪些库。
为智能体导入库
在这个 client.py 文件示例中,智能体导入了通用库,例如 asyncio 和 time(关于这些库的详细解释超出了本书的范围):
import asyncio, time, logging, sys, os
from typing import Dict, Any
from threading import Thread
from fl_main.lib.util.communication_handler import \
init_client_server, send, receive
from fl_main.lib.util.helpers import read_config, \
init_loop, save_model_file, load_model_file, \
read_state, write_state, generate_id, \
set_config_file, get_ip, compatible_data_dict_read, \
generate_model_id, create_data_dict_from_models, \
create_meta_data_dict
from fl_main.lib.util.states import ClientState, \
AggMsgType, ParticipateConfirmationMSGLocation, \
GMDistributionMsgLocation, IDPrefix
from fl_main.lib.util.messengers import \
generate_lmodel_update_message, \
generate_agent_participation_message, \
generate_polling_message
至于从 fl_main.lib.util 导入的 communication_handler、helpers、states 和 messengers 库,它们旨在启用联邦学习的一般功能,请参阅附录,探索内部库。
在导入必要的库之后,您将定义 Client 类。
定义客户端类
让我们定义实现联邦学习客户端核心功能的 Client 类,包括智能体自身的参与机制、模型交换框架以及智能体和聚合器之间的通信接口,以及为在智能体端本地机器学习引擎中使用提供的库:
class Client:
"""
Client class instance with FL client-side functions
and libraries used in the agent's ML engine
"""
然后,您将在 __init__ 函数下初始化 Client 类,如下一节所述。
初始化客户端
以下 __init__ 构造函数中的代码是客户端初始化过程的示例:
def __init__(self):
self.agent_name = 'default_agent'
self.id = generate_id()
self.agent_ip = get_ip()
self.simulation_flag = False
if len(sys.argv) > 1:
self.simulation_flag = bool(int(sys.argv[1]))
config_file = set_config_file("agent")
self.config = read_config(config_file)
self.aggr_ip = self.config['aggr_ip']
self.reg_socket = self.config['reg_socket']
self.msend_socket = 0
self.exch_socket = 0
if self.simulation_flag:
self.exch_socket = int(sys.argv[2])
self.agent_name = sys.argv[3]
self.model_path = f'{self.config["model_path"]}
/{self.agent_name}'
if not os.path.exists(self.model_path):
os.makedirs(self.model_path)
self.lmfile = self.config['local_model_file_name']
self.gmfile = self.config['global_model_file_name']
self.statefile = self.config['state_file_name']
self.round = 0
self.init_weights_flag = \
bool(self.config['init_weights_flag'])
self.is_polling = bool(self.config['polling'])
首先,客户端为自己生成一个唯一的 ID 作为标识符,该标识符将在许多场景中用于执行联邦学习。
第二,客户端通过使用 get_ip() 函数获取自己的 IP 地址。
此外,本实现练习支持模拟运行,其中我们可以在一台机器上运行数据库、服务器和多个智能体的所有联邦学习系统组件。如果需要进行模拟,则 simulation_flag 参数需要设置为 True(有关如何设置模拟模式的说明,请参阅 GitHub 上的 README 文件)。
然后,self.cofig 读取并存储 config_agent.json 的信息。
然后,客户端配置聚合器的信息以连接到其服务器,其中 self.aggr_ip 从智能体配置文件中读取聚合器机器或实例的 IP 地址。
之后,将设置 reg_socket 端口,其中 reg_socket 用于智能体的注册,以及存储为 self.aggr_ip 的聚合器 IP 地址。在这个示例中,reg_socket 的值也可以从智能体配置文件中读取。
用于在模型交换例程中发送本地机器学习模型的 msend_socket,将在代理通过向联邦学习服务器发送消息并接收响应后参与联邦学习过程时进行配置。
当通信不是在 轮询 模式下时,exch_socket 用于接收来自聚合器的全局模型,同时与存储为 self.agent_ip 的代理 IP 地址一起使用。
在本例中,exch_socket 可以根据模拟模式从命令行参数读取或由聚合器决定。
在本例中,当聚合器被设置为能够向连接的代理推送消息时,这在轮询模式下是不成立的,exch_socket 可以由聚合器动态配置。
self.model_path 存储本地和全局模型的路径,可以根据模拟模式从代理配置文件或命令行参数中读取。如果没有目录来保存这些模型文件,它将确保创建该目录。
self.lmfile, self.gmfile, 和 self.statefile 分别是本地模型、全局模型和客户端状态的文件名,它们从代理的配置文件中读取。特别是,在 self.statefile 中,保存了 ClientState 的值。ClientState 是客户端自身的枚举值,其中有一个等待全局模型的状态(waiting_gm),一个用于本地训练的状态(training),一个用于发送本地模型的状态(sending),以及一个拥有更新后的全局模型的状态(gm_ready)。
联邦学习过程的轮次信息,定义为 self.round,初始化为 0,并在模型聚合过程中随着联邦学习轮次的进行而更新,通常聚合器会通知轮次的变化。
self.init_weights_flag 是当系统操作员想要使用某些参数初始化全局模型时使用的标志,如代理配置中所述。
self.is_polling 标志涉及是否在代理和聚合器之间的通信中使用轮询方法。轮询标志必须与聚合器端设置的标志相同。
这里讨论的关于 __init__ 构造函数的代码可以在 GitHub 上的 fl_main/agent 文件夹中的 client.py 文件中找到 (github.com/tie-set/simple-fl)。
既然我们已经讨论了如何初始化客户端模块,在下一节中,我们将探讨参与机制是如何与一些示例代码一起工作的。
代理参与联邦学习周期
这个参与或注册过程是代理能够与其他代理一起参与联邦学习过程所必需的。因此,代理需要被添加到可以发送本地训练的机器学习模型到聚合器的授权代理列表中。
异步的 participate 函数向聚合器发送第一条消息以加入 FL 循环,并将接收状态和通信信息,例如来自聚合器的套接字编号。
代理通过 config_agent.json 文件知道加入 FL 平台的 IP 地址和端口号。当加入 FL 平台时,代理发送包含以下信息的参与消息:
-
agent_name:代理自身的唯一名称。 -
id:代理自身的唯一标识符。 -
model_id:要发送给聚合器的模型的唯一标识符。 -
models:按模型名称键控的模型字典。如果init_flag为False,则模型权重不需要训练,因为它仅由聚合器用于记住模型的形状。 -
init_weights_flag:一个布尔标志,表示发送的模型权重是否应作为基础模型使用。如果为True且没有准备好的全局模型,聚合器将此组本地模型作为第一个全局模型,并发送给所有代理。 -
simulation_flag:如果是模拟运行,则为True;否则为False。 -
exch_socket:等待从聚合器接收全局模型的端口号。 -
gene_time:模型生成的时间。 -
performance_dict:以字典格式存储与模型相关的性能数据。 -
agent_ip:代理自身的 IP 地址。
定义了所有上述参与消息后,代理准备好与聚合器交换模型,实现参与过程的代码如下:
async def participate(self):
data_dict, performance_dict = \
load_model_file(self.model_path, self.lmfile)
_, gene_time, models, model_id = \
compatible_data_dict_read(data_dict)
msg = generate_agent_participation_message(
self.agent_name, self.id, model_id, models,
self.init_weights_flag, self.simulation_flag,
self.exch_socket, gene_time, performance_dict,
self.agent_ip)
resp = await send(msg, self.aggr_ip, self.reg_socket)
self.round = resp[ \
int(ParticipateConfirmaMSGLocation.round)]
self.exch_socket = resp[ \
int(ParticipateConfirmationMSGLocation.exch_socket)]
self.msend_socket = resp[ \
int(ParticipateConfirmationMSGLocation.recv_socket)]
self.id = resp[ \
int(ParticipateConfirmationMSGLocation.agent_id)]
self.save_model_from_message(resp, \
ParticipateConfirmationMSGLocation)
代理读取本地模型以告知聚合器 ML 模型的结构,初始模型不一定需要训练。data_dict 和 performance_dict 分别存储模型及其性能数据。
然后,使用 generate_agent_participation_message 函数包装包含如 ML models 和其 model_id 等信息的消息 msg。
在发送消息时,在这个例子中,使用聚合器的 IP 地址 (aggr_ip) 和注册端口号 (reg_socket) 构建 WebSocket 以连接到聚合器。
通过从 communication_handler 导入的异步 send 函数向聚合器发送消息后,代理从聚合器接收响应消息 resp。响应将包括轮次信息、接收全局模型 exch_socket 的端口号、将本地模型发送到聚合器 msend_socket 的端口号以及更新的代理 ID。
最后,通过调用 save_model_from_message 函数将响应消息中的全局模型保存在本地。
代理的参与机制已经解释。在下一节中,我们将学习模型交换同步的框架。
模型交换同步
模型交换同步,如下面的代码所示,是为了检查代理的状态并根据状态调用适当的函数:
Async def model_exchange_routine(self):
while True:
await asyncio.sleep(5)
state = read_state(self.model_path, self.statefile)
if state == ClientState.sending:
await self.send_models()
elif state == ClientState.waiting_gm:
if self.is_polling == True:
await self.process_polling()
else: pass
elif state == ClientState.training: pass
elif state == ClientState.gm_ready: pass
else: pass
基本上,当客户端存活时,这个过程始终在运行,而while循环则定期用来检查客户端的状态,并在必要时进行下一步操作。
在while循环中,等待几秒钟后,它首先通过read_state函数检查客户端状态。read_state函数中的参数是用来定位存储在本地环境中的状态文件。
正如所述,ClientState具有客户端状态的枚举值,定义了发送本地模型的状态(发送)、等待全局模型的状态(等待 _sgm)、本地训练的状态(训练)和接收更新后的全局模型的状态(gm_ready)。
如果客户端处于发送状态(state == ClientState.sending),这意味着它已准备好将本地训练的模型发送到聚合器。因此,代理调用send_models函数将本地训练的机器学习模型发送到聚合器。
当状态是等待全局模型(state == ClientState.waiting_gm)时,如果开启轮询模式,则通过process_polling从代理向聚合器进行轮询;如果轮询模式关闭,则什么都不做。
如果客户端处于训练状态(state == ClientState.training),这意味着客户端现在正在训练本地模型,只需等待几秒钟,如果需要的话打印训练状态。也可以根据需要添加任何程序。
如果客户端处于gm_ready状态(state == ClientState.gm_ready),这意味着客户端已收到全局模型。此状态将由本地机器学习应用程序处理,它除了显示全局模型的就绪状态外,不做任何事情。
在下一节中,我们将讨论如何实现联邦学习周期中的推送和轮询机制。
推送和轮询实现
一旦代理初始化并确认参与联邦学习过程,它就开始等待从聚合器发送的全局模型。从聚合器接收全局模型有两种方式:推送方法和轮询方法。尽管为了简化,这里没有在联邦学习客户端代码中实现安全套接字层(SSL)或传输层安全性(TSL)框架,但建议支持它们以确保持续的通信安全。
让我们来看看每个通信框架的机制。
从聚合器到代理的推送方法
使用推送方法,聚合器将在全局模型生成后立即将包含全局模型的消息推送到所有连接的代理。
以下代码展示了从聚合器接受和保存全局模型的推送机制:
async def wait_models(self, websocket, path):
gm_msg = await receive(websocket)
self.save_model_from_message( \
gm_msg, GMDistributionMsgLocation)
wait_models异步函数接受websocket作为参数。当聚合器向代理发送消息时,它通过await recieve(websocket)接收gm_msg消息,并通过调用在设计 FL 客户端库部分定义的save_model_from_message函数,将全局模型本地保存。
代理到聚合器的轮询方法
使用polling方法,代理将持续询问(轮询)聚合器,以查看全局模型是否已经形成。一旦创建并准备好发送给连接的代理,轮询的消息将返回给代理,并在响应中包含更新的全局模型。
以下关于process_polling异步函数的代码说明了polling方法:
async def process_polling(self):
msg = generate_polling_message(self.round, self.id)
resp = await send(msg, self.aggr_ip, self.msend_socket)
if resp[int(PollingMSGLocation.msg_type)] \
== AggMsgType.update:
self.save_model_from_message(resp, \
GMDistributionMsgLocation)
else: pass
它首先使用generate_polling_message函数生成要发送给聚合器的轮询消息。在收到聚合器发送的响应消息resp后,如果消息类型是AggMsgType.update,意味着响应消息包含更新的全局模型,它将调用save_model_from_message函数。否则,它不执行任何操作。
上述函数是 FL 客户端的基本但核心功能,这些函数需要作为库被用户端的 ML 应用程序高效地使用。
现在 FL 客户端设计,包括初始化、参与和模型交换,已经解释过了,我们将学习如何设计 FL 客户端库。
设计 FL 客户端库
在本节中,我们将解释如何封装基本函数作为库提供给用户。在本例中,将它们作为库封装的最简单方法将被讨论。这需要根据你的需求和自己的 FL 客户端框架设计进行扩展。通过将 FL 客户端模块作为库封装,开发者将能够轻松地将 FL 客户端的功能集成到本地 ML 引擎中。
让我们从如何定义一个库来启动和注册 FL 客户端开始。
启动 FL 客户端核心线程
为了使本地 ML 应用开发者能够集成 FL 客户端相关的函数,有时需要将它们封装为线程函数。
以下注册 FL 系统中代理的代码简单地将一个participate函数放入asyncio.get_event_loop函数的run_until_complete函数中:
def register_client(self):
asyncio.get_event_loop().run_until_complete( \
self.participate())
此外,start_wait_model_server函数被封装,如下面的代码块所示,其中Thread函数负责持续运行。这样,你将能够在并行运行本地 ML 模块的同时,在 FL 系统处于推送通信模式时,在wait_models线程中接收全局模型:
def start_wait_model_server(self):
th = Thread(target = init_client_server, \
args=[self.wait_models, self.agent_ip, \
self.exch_socket])
th.start()
同样,start_model_exhange_server 函数可以是一个线程,用于运行模型交换例程以同步本地和全局模型,同时本地机器学习模块并行运行。您只需调用以下 start_model_exchange_server 函数作为库来启用此功能:
def start_model_exchange_server(self):
self.agent_running = True
th = Thread(target = init_loop, \
args=[self.model_exchange_routine()])
th.start()
最后,当它们在 Client 类外部被调用时,同时执行所有这三个函数可能是有帮助的。因此,我们引入了以下关于 start_fl_client 的代码,该代码聚合了注册代理、等待全局模型和模型交换例程以启动 FL 客户端核心功能的功能:
def start_fl_client(self):
self.register_client()
if self.is_polling == False:
self.start_wait_model_server()
self.start_model_exchange_server()
FL 客户端的初始化现在被封装到 start_fl_client 中。接下来,我们将定义保存的机器学习模型库。
保存全局模型
虽然 load 和 save 模型函数由 lib/util 中的辅助函数提供,这些将在后面的 附录,探索内部库 中解释,但为机器学习开发者提供一个从聚合器发送的消息中保存全局模型的接口是有帮助的。
以下 save_model_from_message 函数是一个从代理中提取并保存全局模型,并更改客户端状态为 gm_ready 的函数。此函数将消息(msg)和消息位置(MSG_LOC)信息作为参数:
def save_model_from_message(self, msg, MSG_LOC):
data_dict = create_data_dict_from_models( \
msg[int(MSG_LOC.model_id)],
msg[int(MSG_LOC.global_models)],
msg[int(MSG_LOC.aggregator_id)])
self.round = msg[int(MSG_LOC.round)]
save_model_file(data_dict, self.model_path, \
self.gmfile)
self.tran_state(ClientState.gm_ready)
使用 create_data_dict_from_models 库从消息中提取全局模型、模型 ID 和聚合器 ID,并将它们放入字典中。根据接收到的消息,也更新了轮次信息。
然后,使用 save_model_file 库将接收到的全局模型保存到本地文件中,其中指定了数据字典、模型路径和全局模型文件名以保存模型。
接收到全局模型后,它通过调用 tran_state 函数将客户端状态更改为 gm_ready,该状态表示全局模型已准备好由本地机器学习使用,tran_state 函数将在下一节中解释。
定义了保存全局模型的函数后,我们就可以继续下一节,了解如何操作客户端状态。
操作客户端状态
为了操作客户端状态以便它可以逻辑地处理本地和全局模型,我们准备了 read_state 和 tran_state 函数,这些函数可以从代码内部和外部访问。
以下 read_state 函数读取存储在 statefile 中、由 model_path 指定位置的值。使用 ClientState 的枚举值来更改客户端状态:
def read_state(self) -> ClientState:
return read_state(self.model_path, self.statefile)
以下 tran_state 函数更改代理的状态。在这个代码示例中,状态仅在本地 state 文件中维护:
def tran_state(self, state: ClientState):
write_state(self.model_path, self.statefile, state)
接下来,让我们定义可以将本地模型发送到聚合器的函数。
将本地模型发送到聚合器
以下异步的 send_models 函数是关于将本地保存的模型发送到聚合器的:
async def send_models(self):
data_dict, performance_dict = \
load_model_file(self.model_path, self.lmfile)
, _, models, model_id = \
compatible_data_dict_read(data_dict)
msg = generate_lmodel_update_message( \
self.id, model_id, models, performance_dict)
await send(msg, self.aggr_ip, self.msend_socket)
self.tran_state(ClientState.waiting_gm)
它首先使用load_model_file辅助函数提取data_dict和performance_dict,然后根据compatible_data_dict_read函数从data_dict中提取模型及其 ID。然后,使用generate_lmodel_update_message库包装消息,并通过communication_handler的send函数发送到聚合器。之后,通过tran_state函数将客户端状态更改为waiting_gm。再次强调,可以添加 SSL/TSL 框架来确保通信安全,但在此处未实现,以保持联邦学习客户端编码的简单性。
当你想将初始基础模型发送到模型架构的聚合器以进行注册目的时,会调用以下send_initial_model函数。它接受初始模型、样本数量和性能值作为输入,并调用将在本节后面解释的setup_sending_model:
def send_initial_model(self, initial_models, \
num_samples=1, perf_val=0.0):
self.setup_sending_models( \
initial_models, num_samples, perf_val)
当你在联邦学习周期内想要向聚合器发送训练好的本地模型时,会调用以下send_trained_model函数。它接受训练好的模型、样本数量和性能值作为输入,并且只有在客户端状态不是gm_ready时才调用setup_sending_model:
def send_trained_model(self, models, \
num_samples, perf_value):
state = self.read_state()
if state == ClientState.gm_ready:
pass
else:
self.setup_sending_models( \
models, num_samples, perf_value)
以下setup_sending_models函数被设计为内部库,用于设置将本地训练的模型发送到聚合器的过程。它接受模型的参数作为np.array,样本数量作为整数,以及性能数据作为浮点值:
def setup_sending_models(self, models, \
num_samples, perf_val):
model_id = generate_model_id( \
IDPrefix.agent, self.id, time.time())
data_dict = create_data_dict_from_models( \
model_id, models, self.id)
meta_data_dict = create_meta_data_dict( \
perf_val, num_samples)
save_model_file(data_dict, self.model_path, \
self.lmfile, meta_data_dict)
self.tran_state(ClientState.sending)
基本上,这个函数使用generate_model_id辅助函数创建一个唯一的模型 ID,使用create_data_dict_from_models辅助函数创建的data_dict来存储本地机器学习模型数据,以及使用create_meta_data_dict辅助函数创建的meta_data_dict来存储性能数据。然后,所有与模型和性能相关的上述数据都使用save_model_file函数保存在由self.model_path指定的位置。接着,它将客户端状态更改为sending,以便mode_exchange_routine函数可以注意客户端状态的改变,并开始向聚合器发送训练好的本地模型。
既然我们已经了解了将机器学习模型发送到聚合器的库,那么让我们来了解一个在代理端等待全局模型的重要函数。
等待聚合器提供的全局模型
以下wait_for_global_model函数对于持续进行联邦学习周期非常重要:
def wait_for_global_model(self):
while (self.read_state() != ClientState.gm_ready):
time.sleep(5)
data_dict, _ = load_model_file( \
self.model_path, self.gmfile)
global_models = data_dict['models']
self.tran_state(ClientState.training)
return global_models
原则上,该函数会等待客户端状态变为gm_ready。当代理端接收到全局模型时,客户端状态会变为gm_ready。一旦客户端状态变为gm_ready,它将继续从data_dict中加载全局模型,使用load_model_file函数提取,将客户端状态更改为training,并将全局模型返回到本地机器学习模块。
我们已经讨论了如何设计 FL 客户端函数的库。在下一节中,我们将讨论如何将这些库集成到本地机器学习过程中。
本地机器学习引擎集成到 FL 系统中
将 FL 客户端库成功集成到本地机器学习引擎中,对于后续在分布式环境中进行联邦学习至关重要。
如图 5.2所示,GitHub 仓库github.com/tie-set/simple-fl中examples/minimal目录下的minimal_MLEngine.py文件提供了一个将 FL 客户端库集成到最小化 ML 引擎包的示例:

图 5.2 – 最小化机器学习引擎包
在下一节中,我们将解释需要将哪些库导入到本地机器学习引擎中。
导入本地机器学习引擎的库
以下代码显示了导入过程,其中首先导入了通用库,如numpy、time和Dict。这个过程的关键部分是,从fl_main.agent文件夹中的client.py文件导入Client。这样,开发者不需要了解 FL 系统内部太多的代码,只需调用作为库定义的重要功能,正如在设计 FL 客户端库的方向一节中讨论的那样。
本书将不会介绍pip安装打包的内容,但可以使用私有或公共 PyPI 服务器托管客户端代码:
import numpy as np
import time, logging, sys
from typing import Dict
from fl_main.agent.client import Client
在导入必要的库之后,让我们看看为本地训练和测试定义的函数。
定义机器学习模型、训练和测试函数
您首先定义要集成到 FL 系统中的模型、训练和测试函数。在本代码示例中,我们将使用虚拟模型和训练/测试函数,使用户能够理解最小化 FL 流程,而无需被特定的机器学习复杂性所困扰。
被称为init_models的以下函数返回模型的模板(以字典格式),以告知 ML 模型结构。模型不一定需要训练。在这种情况下,模型由model1和model2定义了两个层,每个层分配了一些随机的 NumPy 数组,如下所示:
def init_models() -> Dict[str,np.array]:
models = dict()
models['model1'] = np.array([[1, 2, 3], [4, 5, 6]])
models['model2'] = np.array([[1, 2], [3, 4]])
return models
在初始化模型后,您将设计以下training函数,它可以作为每个 ML 应用的占位符函数:
def training(models: Dict[str,np.array],
init_flag: bool = False) -> Dict[str,np.array]:
# return templates of models to tell the structure
# This model is not necessarily actually trained
if init_flag:
return init_models()
# ML Training. In this example, no actual training.
models = dict()
models['model1'] = np.array([[1, 2, 3], [4, 5, 6]])
models['model2'] = np.array([[1, 2], [3, 4]])
return models
此函数的逻辑顺序应该是接收模型作为输入,训练它们,并返回训练后的本地模型。作为输入参数,它接受具有Dict[str,np.array]格式的模型和init_flag布尔值,指示是否是初始化步骤。
当您想要调用并返回预定义的init_models时,init_flag为True,如果是实际的训练步骤,则为False。
最终,此函数返回分解为 NumPy 数组的训练模型,在这个例子中是一个 Dict[str,np.array] 字典。
在这个虚拟例子中,我们只是提供了虚拟模型,跳过了实际的训练过程。
然后,以下 compute_performance 函数被设计用来计算给定一组模型和测试数据集的模型性能:
def compute_performance(models: Dict[str,np.array], \
testdata) -> float:
# replace with actual performance computation logic
accuracy = 0.5
return
再次强调,在这个例子中,只提供了一个虚拟准确度值 0.5,以保持事情简单。
然后,你可能想要定义以下 judge_termination 函数来决定结束训练过程并退出联邦学习过程的准则:
def judge_termination(training_count: int = 0,
global_arrival_count: int = 0) -> bool:
# Depending on termination criteria, change the return bool value
# Call a performance tracker to check if the current models satisfy the required performance
return True
如何设计这个终止条件取决于你。这个函数接受诸如完成训练过程的数量(training_count)、接收全局模型的次数(global_arrival_count)等参数,返回一个布尔值,其中标志为 True 表示继续联邦学习过程,False 表示停止。在这里,它只提供了一个 True 布尔值,意味着除非代理被强制在函数外部停止,否则联邦学习过程不会停止。
如果需要准备测试数据,你可以定义一个如 prep_test_data 的函数:
def prep_test_data():
testdata = 0
return
在这个例子中,它被设置为 0。
现在测试和训练所需的函数已经定义,我们将客户端库集成到本地机器学习(ML)引擎中,以运行与联邦服务器端组件(如聚合器和数据库)一起工作的联邦学习代理。
将客户端库集成到您的本地 ML 引擎中
现在,一切准备就绪,可以开始你的第一个联邦学习(FL)过程,尽管模型、训练和测试函数都使用虚拟变量设置。
首要任务是创建一个 Client 实例,如下所示,以便你可以调用其库:
# Step1: Create Client instance
cl = Client()
第二,你使用训练函数创建 initial_models,如下所示:
# Step2: Create template models (to tell the shapes)
initial_models = training(dict(), init_flag=True)
之后,通过调用 cl.send_initial_model 并将 initial_models 作为参数,它将初始模型发送到联邦聚合器:
# Step3: Send initial models
cl.send_initial_model(initial_model)
然后,让我们通过调用 cl.start_fl_client() 来启动客户端的联邦学习过程。正如在 启动联邦客户端核心线程 部分中解释的那样,此函数可以同时启动三个过程:注册代理、等待全局模型以及模型交换例程:
# Step4: Start the FL client
cl.start_fl_client()
然后,我们通过有效地集成几个联邦客户端库,设计客户端的本地训练/测试和发送/接收模型的联邦学习周期:
# Step5: Run the local FL loop
training_count, gm_arrival_count = 0, 0
while judge_termination(training_count, gm_arrival_count):
global_models = cl.wait_for_global_model()
gm_arrival_count += 1
global_model_performance_data = \
compute_performance(global_models, prep_test_data())
models = training(global_models)
training_count += 1
perf_value = compute_performance( \
models, prep_test_data())
cl.send_trained_model(models, 1, perf_value)
我们使用 while 循环和 judge_termination 函数来检查系统是否需要退出循环。是否使用 training_count 和 gm_arrival_count 来判断联邦学习周期的终止取决于你。
然后,代理通过 cl.wait_for_global_model() 继续等待全局模型。当从聚合器接收到全局模型后,它提取 global_models,增加 gm_arrival_count,并在 wait_for_global_model 函数中将客户端状态设置为 training 状态。
接下来,使用 compute_performance 函数计算 global_model_performance_data,输入为 global_models 和 prep_test_data。
当在 training 状态下执行 training(global_models) 时,客户端可能会从聚合器接收新的全局模型。这种情况发生在客户端的本地训练速度过慢,聚合器决定利用其他本地模型来创建一组新的全局模型。如果新的全局模型已经到达代理,客户端的状态将变为 gm_ready,并且当前正在训练的 ML 模型将被丢弃。
在本地训练阶段完成,并生成 models 后,代理通过 compute_performance 函数增加 training_count 并计算当前 ML 模型的性能数据 perf_value。
然后,代理尝试通过 cl.send_trained_model 将训练好的本地模型上传到聚合器,并将训练好的模型和之前计算的性能值作为参数。
在 send_trained_model 函数中,客户端状态被设置为 sending。一旦客户端的 model_exchange_routine 观察到状态转换为 sending 状态,它将训练好的本地模型(以二进制文件的形式存储)发送给聚合器。发送模型后,它回到 send_models 函数中的 waiting_gm 状态。
在发送本地模型后,聚合器将其存储在缓冲区中,并等待下一轮全局模型聚合,直到有足够的本地模型由代理上传。
在下一节中,我们将简要介绍如何将图像分类机器学习集成到我们讨论过的联邦学习系统中。
将图像分类集成到联邦系统中的示例
我们通过一个最小示例学习了如何启动一个联邦学习过程。在本节中,我们将给出使用 CNN 进行图像分类(IC)的联邦学习的一个简要示例。
首先,包含图像分类示例代码的包位于 GitHub 仓库 github.com/tie-set/simple-fl 中的 examples/image_classification/ 文件夹,如图 5.3 所示:

图 5.3 – 图像分类包
负责将 IC 算法集成到 FL 系统中的主要代码位于 classification_engine.py 文件中。
当导入库时,我们使用一些额外的文件,包括与 IC 算法相关的 CNN 模型、转换函数和数据管理器。详细信息请参阅 GitHub 代码 github.com/tie-set/simple-fl。
接下来,让我们导入一些标准的 ML 库以及我们讨论过的 FL 代码中的客户端库:
import logging
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from typing import Dict
from .cnn import Net
from .conversion import Converter
from .ic_training import DataManger, execute_ic_training
from fl_main.agent.client import Client
在这种情况下,我们定义 TrainingMetaData,它只提供了将要发送给聚合器并在执行 FedAvg 算法时使用的数据量。聚合算法在 第四章 使用 Python 的联邦学习服务器实现 以及在 第七章 模型聚合 中进行了讨论:
class TrainingMetaData:
# The number of training data used for each round
# This will be used for the weighted averaging
# Set to a natural number > 0
num_training_data = 8000
init_models 函数的内容现在被替换为一个转换为 NumPy 数组的 CNN。它以字典格式返回 CNN 的模板以告知结构:
def init_models() -> Dict[str,np.array]:
net = Net()
return Converter.cvtr().convert_nn_to_dict_nparray(net)
训练函数 training 现在填充了使用 CIFAR-10 数据集的实际训练算法。它以模型和 init_flag 作为参数,并将训练后的模型作为 Dict[str,np.array] 返回。init_flag 是一个 bool 值,如果是初始步骤则为 True,如果是实际训练步骤则为 False。在准备训练数据时,由于批量大小,我们使用一定的阈值进行训练。在这种情况下,阈值是 4。
然后,我们使用 net = Converter.cvtr().convert_dict_nparray_to_nn(models) 创建一个基于 CNN 的聚类全局模型。
我们定义损失函数和优化器如下:
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
然后,实际的训练将通过 trained_net = execute_ic_training(DataManger.dm(), net, criterion, optimizer) 进行,其中 IC 训练的实际代码可以在 ic_training.py 文件中找到。
训练完成后,将返回转换后的模型。
算法总结如下:
def training(models: Dict[str,np.array], \
init_flag: bool=False) -> Dict[str,np.array]:
if init_flag:
DataManger.dm( \
int(TrainingMetaData.num_training_data / 4))
return init_models()
net = \
Converter.cvtr().convert_dict_nparray_to_nn(models)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), \
lr=0.001, momentum=0.9)
trained_net = execute_ic_training(DataManger.dm(), \
net, criterion, optimizer)
models = Converter.cvtr(). \
convert_nn_to_dict_nparray(trained_net)
return models
下面的 compute_performance 函数填充了一个用于计算准确率的算法,这个算法足够简单——只需将正确结果的数量除以总标签的数量。给定一组模型和测试数据集,它使用 models 和 testdata 作为参数计算模型的性能:
def compute_performance(models: Dict[str,np.array], \
testdata, is_local: bool) -> float:
# Convert np arrays to a CNN
net = \
Converter.cvtr().convert_dict_nparray_to_nn(models)
correct, total = 0, 0
with torch.no_grad():
for data in DataManger.dm().testloader:
images, labels = data
_, predicted = torch.max(net(images).data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
acc = float(correct) / total
return acc
judge_termination 和 prep_test_data 函数与最小示例中的函数相同。
客户端库集成到 IC 示例中
现在,一切准备就绪,可以开始 IC 算法了,整合前面函数的所有代码与之前 将客户端库集成到您的本地 ML 引擎 部分中使用的代码相同。请查看 classification_engine.py 文件,确保代码相同,除了显示我们实际发送的数据样本数量的部分。这样,通过仅重写前面的函数,您就可以轻松地将自己的本地 ML 应用程序连接到我们在这里讨论的 FL 系统。请参阅 第六章 运行联邦学习系统并分析结果 中的 运行图像分类及其分析 部分,以检查本节讨论的代码的运行结果。
摘要
在本章中,我们讨论了联邦学习(FL)的客户端实现。在参与 FL 过程中有三个基本但重要的功能:通过推送或轮询机制接收来自聚合器的全局模型,以及在本地训练过程结束后向聚合器发送本地模型。为了有效地实现与 FL 聚合器合作的客户端机器学习引擎,理解客户端状态非常重要。客户端状态包括等待全局模型的状态、表示本地训练正在进行的状态、显示准备发送本地模型的状态,以及拥有更新后的全局模型的状态。
我们还讨论了 FL 客户端库的设计理念,其中核心功能需要有效地打包,以提供对机器学习开发人员和工程师友好的接口。
最后但同样重要的是,我们学习了如何实际使用 FL 客户端库将本地机器学习引擎集成到 FL 系统中,其中我们使用了最小示例和 IC 示例来理解集成过程本身。
在下一章中,我们将实际运行本章和前几章中介绍过的代码,以便我们可以深入了解模型的情况,这些模型通过最小示例以及 IC 示例进行聚合。
第六章:运行联邦学习系统并分析结果
在本章中,你将运行在前几章中讨论过的联邦学习(FL)系统,并分析系统行为和聚合模型的输出结果。我们将首先解释 FL 系统组件的配置,以便正确运行系统。基本上,在安装我们 GitHub 示例提供的简单 FL 系统之后,你首先需要选择运行数据库和聚合模块的服务器机器或实例。然后,你可以运行代理程序连接到已经运行的聚合器。每个代理端配置中都需要正确设置聚合器的 IP 地址。此外,还有一个模拟模式,这样你可以在同一台机器或笔记本电脑上运行所有组件,仅测试 FL 系统的功能。在成功运行 FL 系统的所有模块后,你将能够看到在数据库服务器和代理端设置的路径下创建的数据文件夹和数据库。你将能够检查本地和全局模型,包括训练和聚合的模型,以便你可以从数据文件夹中下载最新的或表现最佳的模型。
此外,你还可以看到在最小引擎和图像分类上运行 FL 系统的示例。通过审查生成的模型和性能数据,你可以理解聚合算法以及聚合器和代理之间模型的实际交互。
在本章中,我们将涵盖以下主要主题:
-
配置和运行 FL 系统
-
理解最小示例运行时会发生什么
-
运行图像分类和分析结果
技术要求
本章中介绍的所有代码文件都可以在 GitHub 上找到(https://github.com/tie-set/simple-fl)。
重要注意事项
你可以使用代码文件用于个人或教育目的。请注意,我们不会支持商业部署,也不会对使用代码造成的任何错误、问题或损害负责。
配置和运行 FL 系统
配置 FL 系统及其环境相对简单,请遵循下一小节中的说明。
安装 FL 环境
首先,要运行前一章中讨论的 FL 系统,请使用以下命令将以下存储库克隆到你想要运行 FL 的机器上:
git clone https://github.com/tie-set/simple-fl
一旦完成克隆过程,请在命令行中将目录更改为simple-fl文件夹。模拟运行可以使用一台机器或使用多个系统进行。为了在一台或多台机器上运行 FL 过程,这些机器包括 FL 服务器(聚合器)、FL 客户端(代理)和数据库服务器,你应该创建一个conda虚拟环境并激活它。
要在 macOS 中创建conda环境,你需要输入以下命令:
conda env create -n federatedenv -f ./setups/federatedenv.yaml
如果你使用的是 Linux 机器,你可以使用以下命令创建conda环境:
conda env create -n federatedenv -f ./setups/federatedenv_linux.yaml
然后,在运行代码时激活conda环境federatedenv。为了你的信息,federatedenv.yaml和federatedenv_linux.yaml文件可以在simple-fl GitHub 仓库的setups文件夹中找到,并包含本书中代码示例所使用的库。
如同 GitHub 仓库的README文件所述,运行此程序主要需要三个组件:数据库服务器、聚合器和代理(们)。如果你想在单台机器上运行模拟,你只需在该机器上安装一个conda环境(federatedenv)即可。
如果你想要创建一个分布式环境,你需要在你想要使用的所有机器上安装conda环境,例如在云实例上的数据库服务器、聚合器服务器以及本地客户端机器。
现在 FL 过程的安装过程已经准备就绪,让我们继续使用配置文件来配置 FL 系统。
使用 JSON 文件为每个组件配置 FL 系统
首先,编辑提供的 GitHub 仓库setups文件夹中的配置 JSON 文件。这些 JSON 文件被数据库服务器、聚合器和代理读取以配置它们的初始设置。再次提醒,配置细节如下所述。
config_db.json
config_db.json文件用于配置数据库服务器。使用以下信息来正确操作服务器:
-
db_ip:数据库服务器的 IP 地址(例如,localhost)。如果你想在云实例上运行数据库服务器,例如在亚马逊网络服务(AWS)EC2 实例上,你可以指定实例的私有 IP 地址。 -
db_socket:数据库和聚合器之间使用的套接字编号(例如,9017)。 -
db_name:SQLite 数据库的名称(例如,sample_data)。 -
db_data_path:SQLite 数据库的路径(例如,./db)。 -
db_model_path:保存所有./db/models的目录路径。
config_aggregator.json
config_aggregator.json文件用于在 FL 服务器中配置聚合器。使用以下信息来正确操作聚合器:
-
aggr_ip:聚合器的 IP 地址(例如,localhost)。如果你想在云实例上运行聚合器服务器,例如 AWS EC2 实例,你可以指定实例的私有 IP 地址。 -
db_ip:数据库服务器的 IP 地址(例如,localhost)。如果你想要连接到托管在不同云实例上的数据库服务器,你可以指定数据库实例的公网 IP 地址。如果你将数据库服务器托管在与聚合器实例相同的云实例上,你可以指定实例的相同私有 IP 地址。 -
reg_socket:代理首次连接到聚合器时使用的套接字编号(例如,8765)。 -
recv_socket: 用于从代理上传本地模型或轮询聚合器的套接字编号。代理将通过与聚合器通信来学习此套接字信息(例如,7890)。 -
exch_socket: 当使用推送方法时,用于从聚合器将全局模型发送回代理的套接字编号。代理将通过与聚合器通信来学习此套接字信息(例如,4321)。 -
db_socket: 数据库和聚合器之间使用的套接字编号(例如,9017)。 -
round_interval: 代理检查是否有足够模型以启动聚合步骤的时间间隔(单位:秒;例如,5)。 -
aggregation_threshold: 需要收集的本地模型百分比,以启动聚合步骤(例如,0.85)。 -
polling: 指定是否使用轮询方法的标志。如果标志为1,则使用轮询方法;如果标志为0,则使用推送方法。此值需要在聚合器和代理之间相同。
config_agent.json
config_agent.json文件用于在 FL 客户端中配置代理。使用以下信息正确操作代理:
-
aggr_ip: 聚合器服务器的 IP 地址(例如,localhost)。如果你想连接到托管在云实例上的聚合器服务器,例如 AWS EC2 实例,你可以指定聚合器实例的公网 IP 地址。 -
reg_socket: 代理首次加入聚合器时使用的套接字编号(例如,8765)。 -
model_path: 代理机器中本地目录的路径,用于保存本地和全局模型以及一些状态信息(例如,./data/agents)。 -
local_model_file_name: 在代理机器中保存本地模型的文件名(例如,lms.binaryfile)。 -
global_model_file_name: 在代理机器中保存全局模型的文件名(例如,gms.binaryfile)。 -
state_file_name: 存储代理状态的文件名(例如,state)。 -
init_weights_flag: 如果权重以特定值初始化,则为1,否则为0,其中权重以零初始化。 -
polling: 指定是否使用轮询方法的标志。如果标志为1,则使用轮询方法;如果标志为0,则使用推送方法。此值需要在聚合器和代理之间相同。
现在,可以使用本节中解释的配置文件配置 FL 系统。接下来,你将在 FL 服务器端运行数据库和聚合器。
在 FL 服务器上运行数据库和聚合器
在本节中,你将在 FL 服务器端配置数据库和聚合器。然后,你将编辑simple-fl GitHub 仓库中setups文件夹中的配置文件。之后,你将首先运行pseudo_db,然后运行server_th,如下所示:
python -m fl_main.pseudodb.pseudo_db
python -m fl_main.aggregator.server_th
重要注意事项
如果数据库服务器和聚合服务器运行在不同的机器上,您需要指定数据库服务器或聚合服务器实例的 IP 地址。数据库服务器的 IP 地址可以在setups文件夹中的config_aggregator.json文件中修改。此外,如果数据库和聚合实例都在公共云环境中运行,这些服务器的配置文件 IP 地址需要是私有 IP 地址。代理需要使用公共 IP 地址和连接套接字(端口号)连接到聚合器,并且连接套接字(端口号)需要打开以接受入站消息。
在您启动数据库和聚合服务器后,您将在控制台中看到如下消息:
# Database-side Console Example
INFO:root:--- Pseudo DB Started ---
在控制台的聚合端,您将看到如下内容:
# Aggregator-side Console Example
INFO:root:--- Aggregator Started ---
在这个聚合服务器背后,模型合成模块每 5 秒运行一次,它开始检查收集到的本地模型数量是否超过了聚合阈值定义的数量。
我们现在已经运行了数据库和聚合模块,并准备好使用 FL 客户端运行最小示例。
使用 FL 客户端运行最小示例
在上一章中,我们讨论了将本地 ML 引擎集成到 FL 系统中的方法。在这里,我们使用一个没有实际训练数据的最小样本来尝试运行已经讨论过的 FL 系统。这个最小示例可以作为实现任何本地分布式 ML 引擎时的模板。
在运行最小示例之前,您应该检查数据库和聚合服务器是否已经启动。然后,运行以下命令:
python -m examples.minimal.minimal_MLEngine
在这种情况下,仅连接了一个具有最小 ML 引擎的代理。因此,聚合会在默认代理上传本地模型时发生。
注意,如果聚合服务器运行在不同的机器上,您需要指定聚合服务器或实例的公共 IP 地址。聚合服务器的 IP 地址可以在setups文件夹中的config_agent.json文件中修改。我们还建议在云实例中运行聚合器和数据库时将polling标志设置为1。
图 6.1 展示了运行数据库服务器时的控制台屏幕示例。

图 6.1 – 数据库端控制台示例
图 6.2 展示了运行聚合器时的控制台屏幕示例:

图 6.2 – 聚合端控制台示例
图 6.3 展示了运行代理时的控制台屏幕示例。

图 6.3 – 代理端控制台示例
现在我们知道了如何运行所有 FL 组件:数据库、聚合器和代理。在下一节中,我们将检查运行 FL 系统时如何生成输出。
数据和数据库文件夹
运行 FL 系统后,您将注意到数据库文件夹和数据文件夹是在您在数据库和代理的配置文件中指定的位置创建的。
例如,db文件夹是在db_data_path下创建的,在config_db.json文件中写入。在数据库文件夹中,您将找到 SQLite 数据库,例如model_data12345.db,其中存储了本地和集群全局模型的元数据,以及一个包含所有由代理上传的实际本地模型和由聚合器创建的全局模型的models文件夹。
图 6.4 展示了在db文件夹中存储的二进制文件格式下的 SQLite 数据库和 ML 模型文件,这些文件是通过运行最小示例代码创建的:

图 6.4 – 存储在 db 文件夹中的 SQLite 数据库和 ML 模型文件的二进制文件格式
data文件夹位于代理设备上的model_path位置,这是一个在config_agent.json中定义的字符串值。在最小示例的运行示例中,以下文件在data/agents/default-agent文件夹下创建:
-
lms.binaryfile: 包含由代理创建的本地模型的二进制文件 -
gms.binaryfile: 包含由聚合器创建的全局模型的二进制文件,发送回代理 -
state: 一个包含整数值的文件,表示客户端自身的状态
图 6.5 展示了代理端数据的结构,包括以二进制文件格式表示的全局和本地 ML 模型,以及反映 FL 客户端状态的文件:

图 6.5 – 包含全局和本地 ML 模型以及以二进制文件格式表示的客户端状态的代理数据
现在我们已经了解了关键数据,如全局和本地模型,存储的位置。接下来,我们将更详细地查看数据库,使用 SQLite。
SQLite 数据库
在db文件夹中创建的数据库可以使用任何工具查看,以显示可以打开***.db格式文件的 SQLite 数据库。数据库表在以下章节中定义。
数据库中的本地模型
图 6.6 展示了与上传的本地模型相关的示例数据库条目,其中每个条目列出了本地模型 ID、模型生成的时间、上传本地模型的代理 ID、轮次信息、性能指标和数据样本数量:

图 6.6 – 与上传的本地模型相关的示例数据库条目
数据库中的集群模型
图 6.7显示了与上传的集群模型相关的样本数据库条目,其中每个条目列出了集群模型 ID、模型创建时间、创建此集群模型的聚合器 ID、轮次信息和数据样本数量:

图 6.7 – 与上传的集群模型相关的样本数据库条目
现在我们已经学会了如何使用最小示例配置和运行联邦学习系统,以及如何检查结果。在下一节中,你将了解联邦学习系统的行为以及当运行最小示例时会发生什么。
理解最小示例运行时发生的情况
逐步理解整个联邦学习系统的行为将有助于你设计启用联邦学习的应用程序,并进一步增强联邦学习系统本身。让我们首先看看当我们只运行一个代理时会发生什么,通过打印代理和聚合器模块的一些过程。
只运行一个最小代理
在运行数据库和聚合器服务器之后,让我们运行最小代理并看看会发生什么。当代理以最小机器学习引擎启动时,你将在代理控制台中看到以下消息:
# Agent-side Console Example
INFO:root:--- This is a minimal example ---
INFO:root:--- Agent initialized —
INFO:root:--- Your IP is xxx.xxx.1.101 ---
当代理初始化用于联邦学习的模型时,它会显示这条消息,如果你查看state文件,它已经进入了发送状态,当联邦学习客户端启动时,将触发向聚合器发送模型:
# Agent-side Console Example
INFO:root:--- Model template generated ---
INFO:root:--- Local (Initial/Trained) Models saved ---
INFO:root:--- Client State is now sending ---
然后,在用start_fl_client函数启动客户端之后,参与消息被发送到聚合器。以下是发送到聚合器的参与消息:
[
<AgentMsgType.participate: 0>, # Agent Message Type
'A89fd1c2d9*****', # Agent ID
'047b18ddac*****', # Model ID
{
'model1': array([[1, 2, 3], [4, 5, 6]]),
'model2': array([[1, 2], [3, 4]])
}, # ML Models
True, # Init weights flag
False, # Simulation flag
0, # Exch Port
1645141807.846751, # Generated Time of the models
{'accuracy': 0.0, 'num_samples': 1}, # Meta information
'xxx.xxx.1.101' # Agent's IP Address
]
发送到聚合器的参与消息包括消息类型、代理 ID、模型 ID、带有 NumPy 的机器学习模型、初始化权重标志、模拟标志、交换端口号、模型生成时间以及性能指标和代理的 IP 地址等元信息。
代理收到聚合器发送的确认连接的欢迎消息,其中还包含以下信息:
# Agent-side Console Example
INFO:root:--- Init Response: [
<AggMsgType.welcome: 0>, # Message Type
'4e2da*****', # Aggregator ID
'23487*****', # Model ID
{'model1': array([[1, 2, 3], [4, 5, 6]]),
'model2': array([[1, 2], [3, 4]])}, # Global Models
1, # FL Round
'A89fd1c2d9*****', # Agent ID
'7890', # exch_socket number
'4321' # recv_socket number
] ---
在聚合器端,在此代理向聚合器发送参与消息后,聚合器确认参与并将此初始模型推送到数据库:
# Aggregator-side Console Example
INFO:root:--- Participate Message Received ---
INFO:root:--- Model Formats initialized, model names: ['model1', 'model2'] ---
INFO:root:--- Models pushed to DB: Response ['confirmation'] ---
INFO:root:--- Global Models Sent to A89fd1c2d9***** ---
INFO:root:--- Aggregation Threshold (Number of agents needed for aggregation): 1 ---
INFO:root:--- Number of collected local models: 0 ---
INFO:root:--- Waiting for more local models to be collected ---
在数据库服务器端控制台中,你还可以检查本地模型是否从聚合器发送过来,并且模型已保存在数据库中:
# DB-side Console Example
INFO:root:Request Arrived
INFO:root:--- Model pushed: ModelType.local ---
INFO:root:--- Local Models are saved ---
在聚合器将全局模型发送回代理后,代理接收并保存它,并将客户端状态从等待 _gm更改为gm_ready,表示全局模型已准备好在本地重新训练:
# Agent-side Console Example
INFO:root:--- Global Model Received ---
INFO:root:--- Global Models Saved ---
INFO:root:--- Client State is now gm_ready ---
这里是聚合器发送给代理的消息,包括全局模型。消息内容包含消息类型、聚合器 ID、集群模型 ID、联邦学习轮次和带有 NumPy 的机器学习模型:
[
<AggMsgType.sending_gm_models: 1>, # Message Type
'8c6c946472*****', # Aggregator ID
'ab633380f6*****', # Global Model ID
1, # FL Round Info
{
'model1': array([[1., 2., 3.],[4., 5., 6.]]),
'model2': array([[1., 2.],[3., 4.]])
} # ML models
]
然后,代理读取全局模型,以便使用它们进行本地训练,并将客户端状态更改为训练:
# Agent-side Console Example
INFO:root:--- Global Models read by Agent ---
INFO:root:--- Client State is now training ---
INFO:root:--- Training ---
INFO:root:--- Training is happening ---
INFO:root:--- Training is happening ---
INFO:root:--- Training Done ---
INFO:root:--- Local (Initial/Trained) Models saved ---
INFO:root:--- Client State is now sending ---
INFO:root:--- Local Models Sent ---
INFO:root:--- Client State is now waiting_gm ---
INFO:root:--- Polling to see if there is any update (shown only when polling) ---
INFO:root:--- Global Model Received ---
INFO:root:--- The global models saved ---
在前面的本地训练过程之后,代理继续将训练好的本地模型发送到聚合器,并将客户端状态更改为waiting_gm,这意味着它正在等待具有轮询机制的全球模型。
这里是发送给聚合器的消息,作为训练好的本地模型消息。消息内容包含消息类型、代理 ID、模型 ID、机器学习模型、模型的生成时间以及如性能数据之类的元数据:
[
<AgentMsgType.update: 1>, # Agent's Message Type
'a1031a737f*****', # Agent ID
'e89ccc5dc9*****', # Model ID
{
'model1': array([[1, 2, 3],[4, 5, 6]]),
'model2': array([[1, 2],[3, 4]])
}, # ML Models
1645142806.761495, # Generated Time of the models
{'accuracy': 0.5, 'num_samples': 1} # Meta information
]
然后,在聚合器中,在本地模型被推送到数据库后,它显示了缓冲区中的变化,收集到的本地模型数量从 0 增加到 1,从而表明已收集足够的本地模型以开始聚合:
# Aggregator-side Console Example
INFO:root:--- Models pushed to DB: Response ['confirmation'] ---
INFO:root:--- Local Model Received ---
INFO:root:--- Aggregation Threshold (Number of agents needed for aggregation): 1 ---
INFO:root:--- Number of collected local models: 1 ---
INFO:root:--- Enough local models are collected. Aggregation will start. ---
然后,第一轮的聚合发生,集群全局模型形成,在收到代理的轮询消息后推送到数据库,并发送给代理。聚合器也可以通过推送方法将消息推回代理:
# Aggregator-side Console Example
INFO:root:Round 1
INFO:root:Current agents: [{'agent_name': 'default_agent', 'agent_id': 'A89fd1c2d9*****', 'agent_ip': 'xxx.xxx.1.101', 'socket': 7890}]
INFO:root:--- Cluster models are formed ---
INFO:root:--- Models pushed to DB: Response ['confirmation'] ---
INFO:root:--- Global Models Sent to A89fd1c2d9***** ---
在数据库服务器端,集群全局模型被接收并推送到数据库:
# DB-side Console Example
INFO:root:Request Arrived
INFO:root:--- Model pushed: ModelType.cluster ---
INFO:root:--- Cluster Models are saved ---
在生成并保存了即将到来的联邦学习轮次的集群模型后,本节中的此过程会重复进行,并且联邦学习轮次将继续使用此交互机制进行。
如果您查看本地和集群全局模型,它们如下所示:
{
'model1': array([[1, 2, 3],[4, 5, 6]]),
'model2': array([[1, 2],[3, 4]])
}
这意味着即使发生聚合,也始终只使用一个固定的模型,因此全局模型与初始模型完全相同,因为这里使用了虚拟训练过程。
我们现在将查看在下一节中运行两个最小代理的结果。
运行两个最小代理
在数据库和聚合器服务器运行的情况下,您可以使用simple-fl/examples/minimal文件夹中的minimal_MLEngine.py文件运行许多代理。
您应该通过指定聚合器的 IP 地址来从不同的本地机器运行两个单独的代理,以将那些代理与最小机器学习示例连接起来。
您也可以通过为单个代理指定不同的端口号来从同一台机器运行多个代理以进行模拟。
在 GitHub 上simple-fl存储库中提供的代码中,您可以通过使用以下命令运行多个代理:
python -m examples.minimal.minimal_MLEngine [simulation_flag] [gm_recv_port] [agent_name]
要进行模拟,应将simulation_flag设置为1。gm_recv_port是从聚合器接收全局模型的端口号。聚合器将通过参与消息的响应通知代理端口号。此外,agent_name是本地代理的名称和存储状态和模型文件的目录名称。这对于每个代理都需要是唯一的。
例如,您可以使用以下命令运行第一个和第二个代理:
# First agent
python -m examples.minimal.minimal_MLEngine 1 50001 a1
# Second agent
python -m examples.minimal.minimal_MLEngine 1 50002 a2
如果需要,您可以编辑setups文件夹中的配置 JSON 文件。在这种情况下,agg_threshold被设置为1。
当您在运行多个代理的最小示例的数据库服务器上运行模拟时,控制台屏幕将类似于图 6.1中的屏幕。
图 6.8 展示了在聚合器服务器上运行使用虚拟 ML 模型的最小示例的模拟控制台屏幕:

图 6.8 – 示例:运行最小示例的聚合器端控制台连接两个代理
图 6.9 展示了在其中一个代理中运行使用虚拟 ML 模型的最小示例的模拟控制台屏幕:

图 6.9 – 示例:代理 1 的控制台运行使用虚拟 ML 模型的最小示例
图 6.10 展示了在另一个代理中运行使用虚拟 ML 模型的最小示例的模拟控制台屏幕:

图 6.10 – 示例:代理 2 的控制台运行使用虚拟 ML 模型的最小示例
现在我们知道了如何使用两个代理运行最小示例。为了进一步了解使用此示例的 FL 流程,我们将回答以下问题:
-
对于简单情况,聚合是否已经正确完成?
-
FedAvg算法是否被正确应用? -
聚合器阈值是否与连接的代理一起工作?
在运行和连接两个代理后,聚合器将等待接收来自两个连接代理的两个模型,如下所示:
# Aggregator-side Console Example
INFO:root:--- Aggregation Threshold (Number of agents needed for aggregation): 2 ---
INFO:root:--- Number of collected local models: 0 ---
INFO:root:--- Waiting for more local models to be collected ---
在这种情况下,聚合器阈值在 setups 文件夹中的 config_aggregator.json 文件中设置为 1.0,因此聚合器需要收集所有连接代理的模型,这意味着它需要从连接到聚合器的所有代理那里接收本地 ML 模型。
然后,它从其中一个代理那里接收一个模型,收集到的本地模型数量增加到 1。然而,由于聚合器仍然缺少一个本地模型,它还没有开始聚合:
# Aggregator-side Console Example
INFO:root:--- Local Model Received ---
INFO:root:--- Aggregation Threshold (Number of agents needed for aggregation): 2 ---
INFO:root:--- Number of collected local models: 1 ---
INFO:root:--- Waiting for more local models to be collected ---
在代理端,在将本地模型发送到聚合器后,它将等待聚合器创建集群全局模型并将其发送回代理。这样,您可以在代理端同步 FL 流程,并在全局模型发送回代理并准备重新训练时自动化本地训练过程。
在聚合器收到另一个本地模型后,收集到的模型数量足够开始聚合过程:
# Aggregator-side Console Example
INFO:root:--- Local Model Received ---
INFO:root:--- Aggregation Threshold (Number of agents needed for aggregation): 2 ---
INFO:root:--- Number of collected local models: 2 ---
INFO:root:--- Enough local models are collected. Aggregation will start. ---
它最终将开始第一轮的聚合,如下所示:
# Aggregator-side Console Example
INFO:root:Round 1
INFO:root:Current agents: [{'agent_name': 'a1', 'agent_id': '1f503*****', 'agent_ip': 'xxx.xxx.1.101', 'socket': 50001}, {'agent_name': 'a2', 'agent_id': '70de8*****', 'agent_ip': 'xxx.xxx.1.101', 'socket': 50002}]
INFO:root:--- Cluster models are formed ---
INFO:root:--- Models pushed to DB: Response ['confirmation'] ---
INFO:root:--- Global Models Sent to 1f503***** ---
INFO:root:--- Global Models Sent to 70de8***** ---
在这里,让我们看看在本地训练的代理端 ML 模型:
# Agent 1's Console Example
INFO:root:--- Training ---
INFO:root:--- Training is happening ---
INFO:root:--- Training Done ---
Trained models: {'model1': array([[1, 2, 3],
[4, 5, 6]]), 'model2': array([[1, 2],
[3, 4]])}
INFO:root:--- Local (Initial/Trained) Models saved ---
此外,让我们看看另一个代理在本地训练的 ML 模型:
# Agent 2's Console Example
INFO:root:--- Training ---
INFO:root:--- Training is happening ---
INFO:root:--- Training Done ---
Trained models: {'model1': array([[3, 4, 5],
[6, 7, 8]]), 'model2': array([[3, 4],
[5, 6]])}
INFO:root:--- Local (Initial/Trained) Models saved ---
与从代理 1 和 2 发送到聚合器的模型一样,如果 FedAvg 被正确应用,全局模型应该是这两个模型的平均值。在这种情况下,代理 1 和 2 的数据样本数量相同,因此全局模型应该是这两个模型的平均值。
因此,让我们看看在聚合器中生成的全局模型:
# Agent 1 and 2's Console Example
Global Models: {'model1': array([[2., 3., 4.],
[5., 6., 7.]]), 'model2': array([[2., 3.],
[4., 5.]])}
收到的模型是两个本地模型的平均值,因此平均已经正确执行。
数据库和数据文件夹是在代理配置文件中指定的model_path下创建的。您可以使用 SQLite 查看器应用程序查看数据库值,并基于模型 ID 查找一些模型。
现在我们已经通过最小示例运行了解了正在发生的事情,在下一节中,我们将使用一个卷积神经网络(CNN)的图像分类模型运行一个真实的机器学习应用。
运行图像分类和分析结果
本例演示了使用此 FL 框架进行图像分类任务的使用。我们将使用著名的图像数据集 CIFAR-10(URL:www.cs.toronto.edu/~kriz/cifar.html),以展示 ML 模型如何通过 FL 过程随时间增长。然而,这个例子只是为了使用我们之前讨论的 FL 系统,并不专注于最大化图像分类任务的性能。
准备 CIFAR-10 数据集
以下是与数据集大小、训练和测试数据、类别数量和图像大小相关的信息:
-
数据集大小:60,000 张图像
-
训练数据:50,000 张图像
-
测试数据:10,000 张图像
-
类别数量:10(飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车)
-
每个类别有 6,000 张图像
-
图像大小:32x32 像素,彩色
图 6.11展示了数据集中 10 个不同类别的样本图片集合,每个类别有 10 个随机图像:
![图 6.11 – 数据集中的类别以及每个类别的 10 个随机图像(图像来自 https://www.cs.toronto.edu/~kriz/cifar.html)]
](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/fdr-lrn-py/img/B18369_06_11.jpg)
图 6.11 – 数据集中的类别以及每个类别的 10 个随机图像(图像来自 https://www.cs.toronto.edu/~kriz/cifar.html)
现在数据集已经准备好了,我们将探讨用于 FL 过程的 CNN 模型。
用于图像分类的 FL 机器学习模型
这是本图像分类示例中使用的 CNN 模型机器学习模型架构的描述。要了解更多关于 CNN 的信息,您可以找到许多有用的学习资源,例如cs231n.github.io/convolutional-networks/:
-
Conv2D
-
MaxPool2D(最大池化)
-
Conv2D
-
3 个全连接层
定义 CNN 模型的脚本已经设计好,可以在 GitHub 上的simple-fl仓库中的examples/image_classification目录下的cnn.py中找到。接下来,我们将使用 FL 系统运行图像分类应用。
如何使用 CNN 运行图像分类示例
如本章开头所述的安装步骤中提到,我们首先使用 federatedenv 安装必要的库,然后安装 torch 和 torchvision:
pip install torch
pip install torchvision
您可以通过 GitHub 上 simple-fl 仓库的 setups 文件夹中的 JSON 配置文件来配置许多设置。有关更多详细信息,您可以在我们的 setups 文档中阅读配置文件的一般描述 (github.com/tie-set/simple-fl/tree/master/setups)。
首先,您可以运行两个代理。您可以通过指定适当的端口号来增加在同一设备上运行的代理数量。
如您所知,您首先可以运行数据库和聚合器:
# FL server side
python -m fl_main.pseudodb.pseudo_db
python -m fl_main.aggregator.server_th
然后,启动第一个和第二个代理以运行图像分类示例:
# First agent
python -m examples.image_classification.classification
_engine 1 50001 a1
# Second agent
python -m examples.image_classification.classification
_engine 1 50002 a2
为了模拟实际的 FL 场景,每个代理可访问的训练数据量可以限制为特定数量。这应该在 classification_engine.py 中的 num_training_data 变量中指定。默认情况下,每轮使用 8,000 张图像(2,000 个批次)。
现在我们可以使用 CNN 模型运行两个代理来测试 FL 过程,让我们通过运行图像分类示例来进一步查看结果。
使用 CNN 进行图像分类的运行评估
性能数据(每个本地模型集群模型的准确率)存储在我们的数据库中。您可以通过访问相应的 .db 文件来查看性能历史。
DataManager 实例(在 ic_training.py 中定义)有一个函数可以返回一批图像及其标签(get_random_images)。您可以使用此函数通过训练的 CNN 在特定图像上显示实际标签和预测标签。
图 6.12 展示了我们在自己一侧进行的实验运行的学习性能图;当您使用自己的设置运行时,结果可能会有所不同:

图 6.12 – 使用 CNN 进行图像分类的 FL 实验运行的学习性能图
再次,因为我们这里只使用两个代理,所以结果看起来略有不同。然而,通过适当的超参数设置、数据量和代理数量,您将能够执行一个产生有意义的 FL 评估,我们希望您自己探索,因为这里的重点是只是如何将实际的 ML 模型连接到这个 FL 环境中。
运行五个代理
您可以通过在终端中指定不同的端口号和代理名称来轻松运行五个代理进行图像分类应用。结果看起来与我们在上一节中讨论的相似,只是连接了实际的 ML 模型(在这种情况下,聚合的 ML 模型是 CNN)。运行五个代理后,数据和数据库文件夹看起来像 图 6.13:

图 6.13 – 每个文件夹中存储的具有代理唯一名称的结果
图 6.14显示了数据库中上传的本地模型,包括本地模型 ID、模型生成时间、上传本地模型的代理 ID、性能指标和轮次信息:

图 6.14 – 数据库中本地模型的信息
如果你查看图6.14中的数据库,可以看到五个代理收集的具有本地性能数据的五个模型。
对于每一轮,这五个本地模型被聚合以生成一个集群全局模型,正如数据库中cluster_models表所示,如图6.15所示。存储集群模型的数据库包含有关集群模型 ID、模型生成时间、创建集群模型的聚合器 ID 以及轮次信息:

图 6.15 – 数据库中集群模型的信息
通过这种方式,你可以连接尽可能多的代理。优化本地机器学习算法的设置以获得 FL 系统中性能最佳的联邦模型取决于你。
摘要
在本章中,我们详细讨论了联邦学习系统的执行情况以及系统将如何根据聚合器和代理之间的交互行为。基于控制台示例结果的逐步解释指导你理解FedAvg算法的聚合过程。此外,图像分类示例展示了 CNN 模型如何连接到联邦学习系统,以及联邦学习过程如何通过聚合提高准确性,尽管这并没有优化以最大化训练结果,而是简化以验证使用 CNN 的集成。
通过本章所学的内容,你将能够设计自己的联邦学习应用,整合本书中介绍的原则和框架,并且能够评估你自己的联邦学习行为,以查看整个联邦学习过程和模型聚合是否正确且一致地进行。
在下一章中,我们将介绍各种模型聚合方法,并展示联邦学习如何与这些聚合算法良好地协同工作。
第七章:模型聚合
在第三章的“模型聚合基础”部分,我们介绍了联邦学习(FL)过程中聚合的概念。回想一下,聚合是联邦学习方法使用每个代理在本地训练的模型来产生具有强大全局性能的模型的方式。很明显,所采用的聚合方法的强度和鲁棒性与最终全局模型的性能直接相关。
因此,根据本地数据集、代理和联邦学习系统层次结构选择合适的聚合方法是实现联邦学习良好性能的关键。实际上,该领域许多出版物的研究焦点是提供这些方法在各种理论场景下的数学保证收敛性。
本章的目标是介绍一些关于聚合方法及其在理想和非理想情况下的收敛性的研究,将这些方法与它们在联邦学习实际应用中出现的不同场景中的优势联系起来。阅读完本章后,你应该能够理解不同的联邦学习场景特征如何要求不同的聚合方法,并且应该对如何实现这些算法有一个大致的了解。
本章将涵盖以下主题:
-
重新审视聚合
-
理解 FedAvg
-
修改非理想情况下的聚合
技术要求
书中展示的 Python 算法实现都可以在ch7文件夹中找到,该文件夹位于github.com/PacktPublishing/Federated-Learning-with-Python/tree/main/ch7。
重要注意事项
你可以使用代码文件用于个人或教育目的。请注意,我们不会支持商业部署,并且不会对使用代码造成的任何错误、问题或损害负责。
对于纯聚合算法,包括辅助代码以显示从预设的本地参数中获取的示例输出。修改本地训练过程的聚合方法需要一个联邦学习系统来运行——对于这些,包括使用 STADLE 的完整实现。此外,纯聚合算法可以通过配置聚合方法直接使用 STADLE 进行测试。有关运行示例的信息可以在相关的README文件中找到。
通过pip安装stadle-client包是运行完整的联邦学习过程示例所必需的。以下命令可以用来执行此安装:
pip install stadle-client
建议使用虚拟环境来隔离stadle-client安装的特定包版本与其他系统上的安装。
重新审视聚合
为了在联邦学习中将聚合的上下文语境化,首先,我们描述了应用联邦学习所必需的系统组件:
-
一组执行联邦学习本地训练部分的计算代理。
-
每个代理都拥有一个本地数据集(静态或动态),在严格的联邦学习场景下,其任何部分都不能被发送给另一个代理。
-
每个代理都拥有一个参数化的模型,可以在本地数据集上进行训练,这个过程产生了模型的本地最优参数集。
-
参数服务器或聚合器,在每个迭代中从代理那里接收本地训练的模型,并返回由所选聚合方法产生的结果模型。
每一轮联邦学习通信都可以分解为以下两个阶段:
-
本地训练阶段,在这个阶段,代理在其本地数据集上对本地模型进行多次迭代训练
-
聚合阶段,在这个阶段,代理将上一阶段训练好的本地模型发送给聚合器,并接收聚合模型作为下一轮本地训练阶段的起始模型。
那么,在聚合阶段,一个代理发送一个本地训练的模型究竟意味着什么?一般的方法是使用定义本地模型的参数集,允许在所有可以以这种方式参数化的模型之间实现一定程度的泛化。然而,第二种方法侧重于在基于梯度的优化方法中,将本地训练期间累积的本地梯度发送给聚合器,代理在每一轮结束时使用接收到的聚合梯度更新他们的模型。虽然这种方法限制了只能使用基于梯度的本地训练方法的模型,但这种方法在训练深度学习模型时的普遍性导致了基于梯度聚合的聚合方法的一个子集。在本章中,我们选择通过 FedAvg 算法的视角来构建模型聚合的框架。
理解 FedAvg
在第三章“联邦学习系统的工作原理”中,介绍了名为 FedAvg 的聚合算法,以帮助阐明一般结构和用具体示例表示之前讨论的更抽象的概念。FedAvg 被用于两个原因:底层算法的简单性和比基于梯度的方法更广泛模型类型的通用性。它还受益于研究者的广泛引用,在提出新的聚合方法时,使用 FedAvg 作为基准在不同理论场景中进行性能分析。这种研究界的关注很可能归因于原始 FedAvg 论文是由第一个将 FL 的概念和好处公之于众的谷歌团队发表的。关于进一步阅读,这篇论文可以在arxiv.org/abs/1602.05629?context=cs找到。
FedAvg 之前有一个称为联邦随机梯度下降(FedSGD)的聚合方法。FedSGD 可以看作是 FedAvg 执行的模型参数平均的梯度聚合类似物。此外,在 FL 的背景下,在 FedAvg 之前还研究了平均模型参数的概念,用于并行化 SGD 方法。本质上,这些并行化 SGD 方法的分析反映了 FedAvg 的独立同分布(IID)情况——这一概念将在后面的章节中讨论。无论如何,FedAvg 的简单性、通用性和流行性使其成为深入研究的好基础,为后面章节中讨论的众多聚合方法提供了背景,这些方法基于或改进了 FedAvg。
以前,FedAvg 仅被呈现为一个算法,它接受具有相应本地数据集大小的模型
,其中总和等于 N,并返回:

如第四章中“聚合本地模型”部分所示,使用 Python 实现的联邦学习服务器实现(simple-fl)使用以下函数根据每个模型本地训练所使用的数据量来计算缓冲模型(当前轮次中客户端发送的模型)的加权平均值:
def _average_aggregate(self,
buffer: List[np.array],
num_samples: List[int]) -> np.array:
"""
Given a list of models, compute the average model (FedAvg).
This function provides a primitive mathematical operation.
:param buffer: List[np.array] - A list of models to be aggregated
:return: np.array - The aggregated models
"""
denominator = sum(num_samples)
# weighted average
model = float(num_samples[0]) / denominator * buffer[0]
for i in range(1, len(buffer)):
model += float(num_samples[i]) / denominator * buffer[i]
return model
原始算法与这种描述差异不大。算法的高级步骤如下:
-
服务器随机抽取 K * C 个客户端,其中 K 是客户端的总数,C 是介于 0 和 1 之间的一个参数。
-
选定的 K * C 客户端接收最新的聚合模型,并开始在他们的本地数据上训练模型。
-
每个客户端在完成一定量的训练后,将其本地训练的模型发送回服务器。
-
服务器计算接收到的模型的参数算术平均值,以计算最新的聚合模型。
可以立即将这种形式表示与我们对联邦学习过程的介绍进行比较,其中 ClientUpdate 为代理执行本地训练,服务器使用相同的加权平均算法进行聚合。一个重要点是,在每一轮中采样一部分客户端进行本地训练和模型传输,允许通过 C 参数进行客户端子采样。这个参数包括实验性地确定各种客户端集大小的收敛速度——在理想情况下,这个值将被设置为 1。
如前所述,FedAvg 是一种理想的联邦学习场景,本质上反映了并行化随机梯度下降的方法。在 并行化随机梯度下降(pSGD)中,目标是利用硬件并行化(例如,在多个核心上并行运行)来加速特定机器学习任务上的 SGD 收敛。为此任务的一种方法是每个核心并行地在数据的一些子集上训练基础模型若干次迭代,然后聚合部分训练好的模型,并使用聚合模型作为下一次训练的基础。在这种情况下,如果将核心视为联邦学习场景中的代理,那么并行化 SGD 方法与理想情况下的 FedAvg 是相同的。这意味着为 pSGD 所做的所有收敛保证和相应的分析都可以直接应用于 FedAvg,假设是理想联邦学习场景。因此,从这项先前工作中可以看出,FedAvg 显示出强大的收敛速度。
在对 FedAvg 进行了所有这些赞誉之后,自然会质疑为什么更复杂的聚合方法甚至有必要。回想一下,在讨论 FedAvg 收敛时,多次使用了“理想联邦学习场景”这个短语。不幸的现实是,大多数实际的联邦学习应用将无法满足该短语所规定的条件之一或多个。
理想联邦学习场景可以分解为三个主要条件:
-
用于训练的本地数据集是 IID(数据集是从相同的数据分布中独立抽取的)。
-
计算代理在计算能力上相对同质。
-
可以假设所有代理都不是对抗性的。
从高层次来看,为什么这些特性在联邦学习场景中是可取的是显而易见的。为了更详细地了解为什么这三个条件是必要的,将在接下来的小节中检查在没有每个条件的情况下 FedAvg 的性能。
数据集分布
为了检验非 IID 情况下的 FedAvg,首先,定义数据集的分布究竟指的是什么非常重要。在分类问题中,数据分布通常指的是与每个数据点相关的真实类别的分布。例如,考虑 MNIST 数据集,其中每个图像是从 0 到 9 的手写数字。如果从数据集中抽取 1,000 个图像的均匀随机样本,每个类别的预期图像数量将是相同的——这可以被认为是一种均匀数据分布。或者,一个包含 910 个数字 0 的图像和 10 个其他数字的图像的样本将是一个严重偏斜的数据分布。
为了将定义推广到分类任务之外,可以将它扩展到指代数据集中存在的特征的分布。这些特征可能是手动制作并提供给模型的(例如线性回归),或者它们可以作为模型管道的一部分从原始数据中提取(例如深度 CNN 模型)。对于分类问题,类分布通常包含在特征分布中,这是由于隐含的信念,即特征足以正确预测类别。查看特征分布的好处是,它关注于数据(相对于关注于任务的类别),允许在机器学习任务中进行泛化。
然而,在实验分析的情况下,从数据集中轻松构建非 IID 样本的能力使得分类任务非常适合测试 FedAvg 在 FL 环境中的鲁棒性以及不同的聚合方法。为了在本节中检验 FedAvg,考虑一个玩具 FL 场景,其中每个代理在前面描述的 MNIST 数据集的数据样本上训练 CNN。有两种主要情况,下面将详细说明。
IID 情况
模型的收敛可以通过使用模型参数空间来表示。具有n个参数的模型参数空间可以被视为一个n-维欧几里得空间,其中每个参数对应于空间中的一个维度。考虑一个初始化的模型;这个模型的初始参数可以表示为参数空间中的一个点。随着局部训练和聚合的发生,这个代表点将在参数空间中移动,最终目标是收敛到空间中的一个点,该点对应于损失或误差函数的最小化局部最优。
这些函数的一个关键点是它们依赖于在本地训练过程中使用的数据 – 当代理之间的数据集是 IID 时,相应的损失/误差函数的最优解在参数空间中相对接近。考虑一个简单的情况,即数据集是 IID,并且所有模型都使用相同的参数初始化。如第三章中联邦学习系统的工作原理的模型聚合基础部分所示,参数空间的一个简化版本可以表示如下:
![图 7.1 – 具有相同初始化和 IID 数据集的模型
![img/B18369_07_01.jpg]
图 7.1 – 具有相同初始化和 IID 数据集的模型
观察两个模型如何从相同的起点(紫色 x)开始,并朝向相同的最优解(紫色点)移动,从而产生接近两个模型共享的最优解的聚合模型。
由于代理之间误差/损失函数的相似性,模型在训练过程中倾向于收敛到相同或相似的最优解。这意味着在每个聚合步骤之后,模型的变化相对较小,导致收敛速度与单本地模型情况相匹配。如果底层数据分布代表真实数据分布(例如,MNIST 中的 10 个不同数字是均匀的),则生成的聚合模型将表现出强大的性能。
接下来,考虑每个模型分别初始化的广义 IID 情况:
![图 7.2 – 具有不同初始化和 IID 数据集的模型
![img/B18369_07_02.jpg]
图 7.2 – 具有不同初始化和 IID 数据集的模型
在这种情况下,观察两个模型如何从不同的起点(粗体/虚线 x)开始,并最初朝向不同的最优解移动,从而产生一个较差的第一个模型。然而,在第一次聚合之后,两个模型从相同的起点开始,并朝向相同的最优解移动,导致与第一个案例相似的收敛。
应该很明显,在第一次聚合步骤之后,这会简化为之前的情况,因为每个模型都从聚合参数开始第二轮。因此,之前所述的收敛属性可以扩展到具有 IID 本地数据集的 FedAvg 的一般情况。
非 IID 情况
在 IID 本地数据集情况下,允许收敛速度与单模型情况相匹配的关键属性是由于它们从相似的数据分布中构建,损失/误差函数的局部最优相似性。在非 IID 情况下,最优相似性通常不再观察到。
以 MNIST 为例,让我们考虑一个有两个代理的 FL 场景,其中第一个代理只有 0 到 4 的数字图像,第二个代理只有 5 到 9 的数字图像;也就是说,数据集不是 IID。这些数据集在局部训练级别本质上会导致两个完全不同的五类分类任务,而不是原始的 10 类分类问题——这将导致第一个代理和第二个代理之间的参数空间最优解完全不同。以下是对这个参数空间的简化表示,其中两个模型具有相同的初始化:

图 7.3 – 不同初始化和非 IID 数据集的模型
现在由于最优解不再共享(分别用三角形/正方形表示粗体/点划线模型的最优解),即使重复聚合也无法创建一个接近任一模型最优解的聚合模型。由于每一轮中每个模型的目标最优解不同,模型在每次局部训练阶段都会发生分歧或漂移。
只有最优解的一小部分将在两个代理的损失/误差函数之间共享。因此,每个模型向局部训练期间未共享的最优解移动的概率很高,导致模型在参数空间中相互漂移。然后,每个聚合步骤都会将模型拉向错误的最优解,逆转局部训练期间取得的进展,并阻碍收敛。请注意,仅仅取不同代理的最优解的平均值,在参数空间中几乎不可能接近任何代理的最优解,因此在这种情况下,持续聚合的结果通常是一个在整个数据集上表现不佳的模型。由于在局部训练期间观察到的随机性,最终可能会收敛到一个共享的最优解,这会导致聚合模型在参数空间中的移动,但这没有理论保证,并且当它发生时,收敛速度将远慢于 IID 情况下的收敛速度。
重要提示
这个 MNIST 示例是非 IID 数据集的理论极端。在实践中,非 IID 数据集可能指的是代理之间数据分布的不同偏差(例如,0 到 4 的数字图像是 5 到 9 的两倍,反之亦然)。差异的严重程度与 FedAvg 的性能相关,因此在较轻的情况下仍然可以达到足够的性能。然而,在这些情况下,FedAvg 的性能通常始终劣于在所有本地数据集上一次性训练单个模型的类似集中式训练任务——FL 能够实现的理想模型。
虽然本节重点介绍了非 IID 数据集引起的问题的统计基础,但下一节将探讨一个更为直接的问题,尤其是在更大规模部署时可能会出现的问题。
计算能力分布
参与联邦学习的代理的一个未声明的假设是,如果给予无限的时间,每个代理都能够执行本地训练。计算能力有限(内存和速度)的代理可能比其他代理花费更多的时间来完成本地训练,或者他们可能需要量化等技术来支持模型和训练过程。然而,在某些轮次中无法完成本地训练的代理将简单地通过阻碍联邦学习过程来阻止收敛。
通常,收敛界限和实验结果关注的是达到一定性能水平所需的通信轮数。在这个指标和上述假设下,收敛与分配给每个代理的计算能力完全无关,因为计算能力只影响完成一轮所需的实际时间。然而,在实际应用中,收敛速度是通过实际花费的时间来衡量的,而不是完成的通信轮数——这意味着完成每一轮的时间与轮数一样重要。这个总时间的指标是,当在参与联邦学习的代理中观察到异构计算能力时,简单的 FedAvg 表现不佳的地方。
具体来说,每轮完成的时间瓶颈在于参与该轮的最慢代理的本地训练时间;这是因为与大多数情况下的训练相比,聚合是极其快速的,并且必须等待所有代理完成本地训练。当所有代理都参与该轮时,这个瓶颈成为最慢的整体代理。在计算能力同质化的情况下,最快代理和最慢代理之间的本地训练时间差异将相对微不足道。在异质化的情况下,单个落后代理将大大减少 FedAvg 的收敛时间,并导致更快代理在等待接收聚合模型时产生显著的空闲时间。
两个针对完全参与代理的 FedAvg 的修改可能最初看起来可以解决这个问题;然而,两者都有缺点,导致性能次优:
-
一种方法是依靠每轮的代理子采样,导致每轮发生落后效应的概率取决于代理的数量和每轮采样的样本大小。在只有少数落后代理的情况下,这可能足够,但随着这个数量的增加,问题会成比例地恶化,并且并不能完全消除问题发生的可能性。此外,小样本大小会失去从更多代理的聚合中获得的鲁棒性优势。
-
第二种方法是允许所有代理在每个回合开始时开始本地训练,并在接收到一定数量的模型后提前开始聚合。这种方法的好处是能够在不大大限制每个回合参与聚合的代理数量的情况下,完全消除拖沓效应。然而,它会导致最慢的代理在整个回合中都不会参与聚合,这实际上减少了活跃代理的数量,并可能限制训练期间使用的数据的多样性。此外,那些太慢而无法参与聚合的代理将进行无益的计算工作。
很明显,无论在每个回合结束时应用于接收到的模型的最终聚合方法是什么,都需要基于可用的计算能力进行一些本地调整,以便有效地执行聚合。
非独立同分布案例和异构计算能力案例都关注 FL 系统的一般容易观察到的属性,并在一定程度上受到行政控制。我们接下来提出的案例与此不同,因为它挑战了在考虑实际 FL 系统时的一项关键假设。
防御对抗性代理
到目前为止,一直假设每个参与 FL 场景的代理总是以期望的方式行事;也就是说,积极且正确地在本地训练接收到的模型,并参与模型向聚合器或从聚合器传输。这在研究环境中很容易实现,其中联邦设置被模拟,代理被单独控制;然而,代理行为正确的这种假设在实践中并不总是成立。
一个不涉及针对性恶意意图的例子是代理向聚合器传输的模型权重中的错误。这可能发生在代理使用的数据集有缺陷或训练算法实现不正确(参数数据在传输过程中的损坏也是可能的)。在最坏的情况下,这可能导致一个或多个模型的参数在统计上等同于随机噪声。当随机噪声的 L2 范数(n 维张量向量大小的扩展)不显著大于有效模型的范数时,FedAvg 将遭受与故障代理与所有代理的比例成比例的性能损失——当这个比例较小时,这是相对可以接受的。然而,即使只有一个故障代理,如果代理噪声的范数显著较高,它也会导致几乎随机的聚合模型。这是由于 FedAvg 聚合过程中内部执行算术平均的性质。
当代理可以被恶意对手控制时,问题变得更加严重。一个拥有足够信息的恶意代理可以通过对其提交的模型参数进行大量修改,在聚合后产生任何期望的模型。即使没有直接了解其他代理的模型参数和相关权重,恶意代理也可以利用在后续回合中本地模型和聚合模型之间的相对较小变化,将之前的聚合模型作为对预期本地模型参数的估计。
因此,FedAvg 在 FL 环境中对随机和受控的对抗性代理提供的鲁棒性很少或没有。虽然一种可能的缓解方法是对代理进行单独监控并防止对抗性代理传输模型,但在识别这些代理所需的时间内,最终模型的收敛可能已经受到了重大损害。
现在应该很清楚,FedAvg 在这些非理想情况下牺牲了鲁棒性以换取计算的简单性。不幸的是,由于与研究环境相比缺乏控制,这种鲁棒性是 FL 实际应用中的一个关键考虑因素。下一节将重点介绍实现针对本节中提出的三个非理想情况鲁棒性的方法。
修改聚合以适应非理想情况
在实际的联邦学习(FL)应用中,上述构成理想 FL 场景的假设通常并不成立;因此,可能需要使用替代的聚合方法来最佳地执行 FL。本节的目标是介绍针对异构计算能力、对抗性代理和非独立同分布(non-IID)数据集的聚合方法示例,按照难度顺序排列。
处理异构计算能力
如前所述,在这种情况下,理想的聚合方法始终避免出现落后效应(straggler effect),同时最大化参与 FL 的代理数量,并允许所有代理在一定程度上做出贡献,无论计算能力差异如何。当代理的本地训练时间显著长于大多数代理时,它们在某一回合中成为落后者。因此,有效地解决这个问题实际上需要在代理级别上在本地训练过程中具有一定的适应性,基于每个代理可用的计算能力。
手动调整
实现这一点的简单方法是根据每次迭代所需的时间来改变本地训练迭代的次数。换句话说,本地训练时间是固定的,每个代理尽可能在这个时间内完成尽可能多的迭代,而不是执行固定数量的迭代。这可以简单地消除落后者问题,但如果必须分配大量的本地训练时间给慢速代理以有意义地贡献,因为快速代理可能执行了过多的本地训练迭代而导致模型漂移,这可能会导致性能不佳。这可以通过设置最大本地训练迭代次数来缓解。然而,必须仔细平衡分配的本地训练时间,以便慢速代理有足够的时间产生足够的模型,同时防止快速代理在达到最大迭代次数后闲置。此外,如何预先确定这样的阈值以实现最佳性能,而不是依赖于实验结果来寻找最佳配置,目前还不清楚。
自动调整 – FedProx
一种称为 FedProx 的聚合方法遵循了基于计算能力动态调整每个代理的本地训练过程的相同方法,同时修改了本地训练的终止条件,以帮助进行收敛的理论分析。具体来说,固定的本地训练迭代次数被替换为训练循环的终止条件,该条件可以适应具有不同计算能力的代理。
这种终止条件的潜在概念是 γ-不精确解,当 γ-不精确最优点的梯度幅度小于本地训练开始时梯度幅度的 γ 倍时,该条件得到满足。直观地说,γ 是介于 0 和 1 之间的一个值,值越接近 0,由于更严格的终止条件,会导致更多的本地训练迭代。因此,γ 允许参数化代理的计算能力。
使用终止条件方法的一个潜在问题是,由于严格的条件,在多次本地训练迭代后,局部训练模型可能会从聚合模型中发散。为了解决这个问题,FedProx 在被最小化的目标函数中添加了一个近端项,等于以下内容:

在这里,
表示接收到的聚合模型权重。
近端项惩罚当前权重与聚合模型权重之间的差异,通过 μ 参数参数化的强度限制上述本地模型发散。从这两个概念出发,FedProx 允许每个代理执行与每个代理的计算能力成比例的变量次数,而无需为每个代理手动调整迭代次数或分配一定量的训练时间。由于添加了近端项,FedProx 需要使用基于梯度的优化方法才能工作——有关底层理论和与 FedAvg 的比较的更多信息,可以在原始论文中找到(该论文位于 arxiv.org/abs/1812.06127)。
实现 FedProx
由于 FedProx 对 FedAvg 的修改都是在客户端进行的,因此 FedProx 的实际实现完全由对本地训练框架的修改组成。具体来说,FedProx 涉及本地训练的新终止条件,以及将约束项添加到本地损失函数中。因此,使用本地训练代码的示例可以帮助精确地说明如何集成 FedProx。
让我们考虑以下使用 PyTorch 的通用训练代码:
agg_model = ... # Get aggregate model – abstracted out of example
model.load_state_dict(agg_model.state_dict())
for epoch in range(num_epochs):
for batch_idx, (inputs, targets) in enumerate(trainloader):
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
让这段代码在每个回合使用接收到的聚合模型在本地数据集上执行 num_epochs 个训练周期。FedProx 的第一个必要修改是将固定的训练周期数替换为动态终止条件,检查是否已找到以聚合模型为初始模型的 γ-不精确解。为此,必须存储整个训练数据集上聚合模型和当前本地模型的总体梯度——这可以按以下方式执行:
agg_model = ... # Get aggregated model from aggregator
model.load_state_dict(agg_model.state_dict())
agg_grad = None
curr_grad = None
gamma = 0.9
mu = 0.001
两个 FedProx 参数 gamma 和 mu 的值已设置,并定义了存储聚合模型和最新本地模型梯度的变量。
我们然后使用这些梯度变量定义本地训练的 γ-不精确新终止条件:
def gamma_inexact_solution_found(curr_grad, agg_grad, gamma):
if (curr_grad is None):
return False
return curr_grad.norm(p=2) < gamma * agg_grad.norm(p=2)
在每次训练循环迭代之前检查此条件,以确定何时停止本地训练。创建 total_grad 变量以存储在反向传播期间从每个小批量中创建的累积梯度:
model.train()
while (not gamma_inexact_solution_found(curr_grad, agg_grad, gamma)):
total_grad = torch.cat([torch.zeros_like(param.data.flatten()) for param in model.parameters()])
for batch_idx, (inputs, targets) in enumerate(trainloader):
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
为了计算近端项,计算聚合模型和最新本地模型的权重。从这些权重中,计算近端项并将其添加到损失项中:
curr_weights = torch.cat([param.data.flatten() for param in model.parameters()])
agg_weights = torch.cat([param.data.flatten() for param in agg_model.parameters()])
prox_term = mu * torch.norm(curr_weights - agg_weights, p=2)**2
loss += prox_term
计算梯度并将其添加到存储在 total_grad 中的累积和中:
loss.backward()
grad = torch.cat([param.grad.flatten() for param in model.parameters()])
total_grad += grad
optimizer.step()
最后,在完成当前本地训练迭代后,我们更新 agg_grad(如果梯度是用聚合权重计算的)和 curr_grad:
if (agg_grad == None):
agg_grad = total_grad
curr_grad = total_grad
这些修改使得 FedProx 可以在 FedAvg 之上实现。使用 FedProx 的完整 FL 示例可以在github.com/PacktPublishing/Federated-Learning-with-Python/tree/main/ch7/agg_fl_examples/cifar_fedprox_example找到。
处理异构计算能力场景的辅助方法,当观察到轻微异质性时有助于提高计算效率的想法是聚合中的补偿。考虑聚合发生时接收到的模型数量超过某个阈值的情况(通常,这小于参与代理的数量)。使用这个阈值可以减轻拖沓效应;然而,较慢代理所做的每轮工作最终都会被丢弃,导致训练效率低下。
补偿的核心思想是允许在某一轮次中由较慢的代理进行的本地训练被包含到下一轮次的模型聚合中。在下一轮次中,通过在聚合期间乘以用于加权平均的权重和惩罚项来补偿模型的年龄。通过这样做,较慢的代理可以获得的训练时间可以比快速代理多两到三倍,同时避免拖沓效应。为了防止较慢的代理需要过多的额外训练时间,需要轻微的异质性。这是由于在经过多轮之后给予模型的关联惩罚;它将足够严重,足以有效地导致没有贡献并减少补偿聚合 - 这是必要的,以防止过旧的模型阻碍聚合模型的收敛。
最后,我们检查帮助解决第三个非理想属性的方法,即某些代理子集被对手控制或以其他方式表现出不可取的行为。
对抗性代理
在上一节中,已经表明,在存在对抗性代理的情况下,FedAvg 的核心问题是对聚合过程中使用的底层算术平均值的异常值缺乏鲁棒性。这自然引发了一个问题:这种均值是否可以以提供这种鲁棒性的方式估计。答案是鲁棒均值估计器类别。有许多这样的估计器,它们在鲁棒性、与真实算术平均值的距离和计算效率之间提供了不同的权衡。
作为以下聚合方法实现的基,考虑以下通用聚合函数:
def aggregate(parameter_vectors):
# Perform some form of aggregation
return aggregated_parameter_vector
此函数接受一个参数向量的列表,并返回结果聚合参数向量。
现在我们将检查鲁棒均值估计器的三个示例实现。
使用几何中值进行聚合
样本的空间中位数是指使自身与样本之间的 L1 距离之和最小的点。从概念上讲,这与算术平均数相似,算术平均数是使自身与样本之间的 L2 距离之和最小的点。使用 L1 距离可以提供对异常值更大的鲁棒性;事实上,只有当至少一半的点来自对抗性代理时,才能在空间中位数中诱导出任意点。然而,空间中位数不能直接计算,而是依赖于数值近似或迭代算法来计算。
要迭代地计算几何平均数,可以使用 Weiszfeld 算法如下:
def geometric_median_aggregate(parameter_vectors, epsilon):
vector_shape = parameter_vectors[0].shape
vector_buffer = list(v.flatten() for v in parameter_vectors)
prev_median = np.zeros(vector_buffer[0].shape)
delta = np.inf
vector_matrix = np.vstack(vector_buffer)
while (delta > epsilon):
dists = np.sqrt(np.sum((vector_matrix - prev_median[np.newaxis, :])**2, axis=1))
curr_median = np.sum(vector_matrix / dists[:, np.newaxis], axis=0) / np.sum(1 / dists)
delta = np.linalg.norm(curr_median - prev_median)
prev_median = curr_median
return prev_median.reshape(vector_shape)
该算法利用了这样一个事实:一组点的空间中位数是使该集合上欧几里得距离之和最小的点,在每个迭代中执行一种加权最小二乘法,权重与点与当前中位数估计的欧几里得距离成反比。
使用坐标中位数进行聚合
坐标中位数是通过取样本中每个坐标的中值来构建的,正如其名称所暗示的。这个中值可以直接计算,与空间中位数不同,并且由于单变量统计中中位数的性质,直观上提供了对异常值的类似鲁棒性。然而,不清楚所得到的模型在数据集性能和收敛性方面是否显示出与算术平均数有任何理论上的相似性。
NumPy 使得实现这个函数变得非常简单,如下所示:
def coordinate_median_aggregate(parameter_vectors):
return np.median(parameter_vectors, axis=0)
很明显,坐标中位数比空间中位数更易于计算,这是以牺牲理论保证为代价来换取速度的。
使用 Krum 算法进行聚合
另一种方法是,在聚合之前从对抗性代理中隔离异常值点。这种方法的最著名例子是Krum 算法,在该算法中,在聚合之前执行基于距离的评分,作为定位异常值点的一种手段。
具体来说,Krum 算法首先计算每个点的成对 L2 距离——这些距离随后被用来计算每个点的分数,等于n-f-2个最小的 L2 距离之和(f是一个设置好的参数)。然后,Krum 输出具有最低分数的点,实际上返回了与f个被忽略的异常值点具有最小总 L2 距离的点。或者,Krum 使用的评分方法可以在计算算术平均值之前修剪异常值点。在两种情况下,对于足够大的n和2f+2 < n,收敛率与非对抗情况下的 FedAvg 相似。有关 Krum 算法的更多信息,可以在原始论文中找到,该论文位于papers.nips.cc/paper/2017/hash/f4b9ec30ad9f68f89b29639786cb62ef-Abstract.html。
Krum 算法可以按以下方式执行聚合:
def krum_aggregate(parameter_vectors, f, use_mean=False):
num_vectors = len(parameter_vectors)
filtered_size = max(1, num_vectors-f-2)
scores = np.zeros(num_vectors)
for i in range(num_vectors):
distances = np.zeros(num_vectors)
for j in range(num_vectors):
distances[j] = np.linalg.norm(parameter_vectors[i] - parameter_vectors[j])
scores[i] = np.sum(np.sort(distances)[:filtered_size])
if (use_mean):
idx = np.argsort(scores)[:filtered_size]
return np.mean(np.stack(parameter_vectors)[idx], axis=0)
else:
idx = np.argmin(scores)
return parameter_vectors[idx]
注意,已经包含了一个标志来决定应该使用两种 Krum 聚合方法中的哪一种(单选与修剪平均值)。向量化距离计算是可能的,但由于预期参数向量较大且代理数量较小,迭代方法被优先考虑。
非 IID 数据集
通过与独立同分布(IID)数据集合作,FL(联邦学习)所获得的理论基础在实现高性能聚合模型方面发挥着重要作用。从高层次来看,这可以通过不同数据集中模型学习到的差异来解释。当应用数据集无关的聚合方法时,无法对这些模型的收敛性做出理论保证——除非对数据集的非 IID 性质施加约束。关键阻碍因素是局部模型在参数空间中向非共享最优解移动的高概率,导致在每个局部训练阶段之后,局部模型与聚合模型之间的一致漂移。
有一些方法试图根据局部机器学习任务对聚合模型所做的修改进行限制,依赖于深度学习模型的过度参数化来找到相对分离的参数子集以优化每个任务的聚合模型。这种聚合方法之一是FedCurv,它使用先前聚合模型的 Fisher 信息矩阵作为局部训练期间辅助参数修改的调节器。然而,在实际应用中,对于极端非 IID 情况,这种方法鲁棒性的测试可能需要进一步进行,以确保可接受的性能。
实现 FedCurv
FedCurv 的实现涉及对标准 FedAvg 方法的两个关键修改。首先,本地损失函数必须修改以包含包含上一轮汇总 Fisher 信息的正则化项。其次,必须正确计算和汇总参数的 Fisher 信息矩阵,以便在下一轮中使用。
如实现 FedProx部分所示,本地训练示例代码将被再次使用,以展示 FedCurv 的实现。在第四章 使用 Python 实现联邦学习服务器中,我们了解到一个模型转换层允许聚合器对框架无关的模型表示进行操作。以前,这些表示只包含原始模型的相关参数;然而,这种无关表示实际上允许聚合任何所需的参数,甚至那些与真实模型参数只有松散联系的那些参数。这意味着次要参数可以捆绑并随本地模型发送,汇总后,在下一轮中与汇总模型分离。
在 FedCurv 中,有两组参数必须在本地计算并汇总以用于下一轮;因此,可以假设这些参数在训练后与本地模型一起发送,并在训练前与汇总模型分离,以简化示例代码(此功能的实现很简单)。因此,如前所述,FedCurv 的两个关键修改可以简化为在本地训练模型后计算 Fisher 信息参数,并使用接收到的汇总 Fisher 信息参数计算正则化项。
Fisher 信息矩阵指的是模型对数似然函数的梯度相对于其参数的协方差,通常在现有数据上经验性地评估。FedCurv 仅利用此矩阵的对角线元素,即梯度参数之间的方差及其零的期望值。
在高层次上,这个方差项可以被视为一个估计,即参数在改变模型在数据上的性能方面的影响程度。这个信息对于防止在本地训练其他代理时修改对某个数据集上良好性能至关重要的参数至关重要——这是 FedCurv 背后的基本思想。
将模型性能的度量从对数似然梯度放宽到任何目标函数的梯度,允许在计算使用基于梯度的优化方法(如深度学习模型)的模型的方差项时直接使用反向传播期间计算的梯度项。具体来说,参数的方差项等于其相应梯度项的平方,允许直接从本地训练期间计算的净梯度中计算这些项。
首先,我们创建两个变量来存储代理的最新 Fisher 信息参数和接收到的聚合 Fisher 信息参数,这些参数用于确定来自其他代理的 Fisher 信息。FedCurv 的 lambda 参数值是固定的,total_grad被初始化为一个容器,用于存储每个训练循环的累积梯度:
agg_model = ... # Get aggregated model from aggregator
model.load_state_dict(agg_model.state_dict())
fisher_info_params = ... # Initialize at start, then maintain to store past round parameters
agg_fisher_info_params = ... # Separate aggregate Fisher information parameters from aggregate model parameters
# Only consider other agents, and convert to PyTorch tensor
agg_fisher_info_params = {k:torch.tensor(agg_fisher_info_params[k] - fisher_info_params[k]) for k in fisher_info_params.keys()}
# Scaling parameter for FedCurv regularization term
fedcurv_lambda = 1.0
total_grad = {i:torch.zeros_like(param.data) for i,param in enumerate(model.parameters())}
然后,我们从模型权重和聚合 Fisher 信息参数中计算 FedCurv 正则化项。这个项由 lambda 加权,并在计算梯度之前添加到损失项中:
model.train()
for epoch in range(num_epochs):
for batch_idx, (inputs, targets) in enumerate(trainloader):
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
for i,param in enumerate(model.parameters()):
# Factor out regularization term to use saved fisher info parameters
reg_term = (param.data ** 2) * agg_fisher_info_params[f'fedcurv_u_{i}']
reg_term += 2 * param.data * agg_fisher_info_params[f'fedcurv_v_{i}']
reg_term += (agg_fisher_info_params[f'fedcurv_v_{i}'] ** 2) / agg_fisher_info_params[f'fedcurv_u_{i}']
loss += fedcurv_lambda * reg_term.sum()
然后,在更新模型权重之前,我们计算并存储梯度到total_grad中:
loss.backward()
for i,param in enumerate(model.parameters()):
total_grad[i] += param.grad
optimizer.step()
最后,我们计算并存储代理的最新 Fisher 信息参数,以便在下一轮中使用:
for i,param in enumerate(model.parameters()):
fisher_info_params[f'fedcurv_u_{i}'] = (total_grad[i] ** 2).numpy()
fisher_info_params[f'fedcurv_v_{i}'] = ((total_grad[i] ** 2) * param.data).numpy()
因此,可以在 FedAvg 之上使用框架无关的聚合来实现 FedCurv。使用 FedCurv 的完整 FL 示例可以在github.com/PacktPublishing/Federated-Learning-with-Python/tree/main/ch7/agg_fl_examples/cifar_fedcurv_example找到。
数据共享方法
为了取得进一步进展,需要对 FL 场景的外部方面进行更改。例如,假设数据隐私限制被放宽,使得每个代理的本地数据集的小子集可以与其他代理共享。这种数据共享方法允许在本地数据分布中实现与共享数据量成比例的同质性,但以牺牲 FL 的关键平稳数据属性为代价,这使得它在许多以隐私为导向的应用中变得有吸引力。因此,数据共享方法通常不适合大多数应用。
通过微调实现个性化
当数据集是独立同分布(IID)时,产生一个在本地数据集上表现出强大性能的单个模型并不容易。然而,如果从 FL 过程中移除单个模型限制会发生什么?如果目标是产生在训练所进行的相同边缘设备上表现良好的本地模型,移除单个模型限制允许使用在精确数据分布上训练的不同本地模型,这些数据分布是推理应用的地方。
这个概念被称为个性化,其中代理使用针对本地数据分布调整过的聚合模型版本来实现强大的性能。这种方法的关键点是平衡本地训练模型的本地性能、全局性能以及每一轮接收到的聚合模型的鲁棒性。实现这一目标的一种方法是在每一轮中,每个代理都保持其本地模型,通过每一轮将前一个本地模型和接收到的聚合模型的加权平均值更新本地模型。
或者,考虑一种放松,允许在每一轮中产生多个聚合模型。在本地数据分布可以被聚类成几个分离组的情况下,分布感知聚合允许对属于同一分布聚类的模型组选择性地应用聚合方法。
这种方法的例子之一是基于性能的邻近选择(PENS)算法,其中代理在第一阶段从其他代理那里接收本地训练的模型,并在自己的本地数据集上对其进行测试。利用在相似数据集上训练的模型将比在差异数据集上训练的模型表现更好的假设,代理随后确定具有相似数据分布的其他代理的集合,允许在第二阶段只与相似代理进行聚合。
第二种方法是在本地模型和全局聚合模型之间添加一个中间聚合步骤,称为集群模型。通过利用关于代理数据分布的知识或通过动态分配方法,具有相似数据分布的代理可以被分配到一个集群聚合器,由于其代理拥有独立同分布的数据集,因此该聚合器产生的模型强度较大。
平衡集群模型的表现和全局聚合的鲁棒性导致了半全局模型的概念,在这种模型中,可以从集群模型中选择子样本(可能基于数据分布)来创建一个较小的部分全局聚合模型集,这些模型集可以保持性能和鲁棒性。因此,集群和半全局模型方法对聚合和实现完全分布式联邦学习系统都有益。
摘要
本章的目标是提供一个关于当前聚合知识的概念概述,这是联邦学习中的关键理论步骤,允许每个代理执行的非连接训练以最小的传输需求汇总在一起。FedAvg 是一个简单但出奇强大的聚合算法,在理想的联邦学习场景中表现良好。当使用具有相似计算能力的机器在独立同分布数据集上进行训练,并且没有对抗性或其他表现不佳的代理时,这种场景得以实现。
不幸的是,在现实世界中部署联邦学习系统时,这些条件往往无法满足。为了解决这些问题,我们引入并实施了修改后的聚合方法:FedProx、FedCurv 和三种不同的鲁棒均值估计器。阅读完这一章后,你应该对实际联邦学习应用中必须考虑的因素有了一个坚实的理解,并且应该能够将这些算法集成到这些应用中。
在下一章中,我们将通过几个玩具示例深入探讨一些现有的联邦学习框架,以展示每个框架提供的功能。
第三部分 联邦学习应用的生产化
在这部分,你将了解现有的联邦学习(FL)框架,例如TensorFlow Federated(TFF)、PySyft、Flower 和 STADLE,并学习它们的库以及如何实际运行这些框架。此外,通过了解全球范围内正在实施的当前和潜在用例,特别是全球企业公司中的用例,你将了解现实世界中的联邦学习情况。本书通过探讨联邦学习的未来趋势和发展,来理解人工智能技术本身的发展方向,以展望智慧驱动的未来。
本部分包括以下章节:
-
第八章, 介绍现有的联邦学习框架
-
第九章, 联邦学习应用的关键用例案例研究
-
第十章, 未来趋势与发展
第八章:介绍现有的联邦学习框架
本章的目标是介绍现有的联邦学习(FL)框架和平台,将每个平台应用于涉及玩具机器学习(ML)问题的联邦学习场景。本章关注的平台是 Flower、TensorFlow Federated、OpenFL、IBM FL 和 STADLE——选择这些平台背后的想法是通过涵盖现有的 FL 平台范围来帮助你。
到本章结束时,你应该对如何使用每个平台进行联邦学习有一个基本的了解,并且你应该能够根据其相关的优势和劣势选择一个平台用于联邦学习应用。
在本章中,我们将涵盖以下主题:
-
现有 FL 框架的介绍
-
使用现有框架在电影评论数据集上实现示例 NLP FL 任务
-
使用现有框架实现示例计算机视觉 FL 任务,使用非-IID 数据集
技术要求
你可以在本书的 GitHub 仓库中找到本章的补充代码文件:
https://github.com/PacktPublishing/Federated-Learning-with-Python
重要提示
你可以使用代码文件进行个人或教育目的。请注意,我们不会支持商业部署,并且不会对使用代码造成的任何错误、问题或损害负责。
本章中的每个实现示例都是在运行 Ubuntu 20.04 的 x64 机器上运行的。
NLP 示例的训练代码实现需要以下库来运行:
-
Python 3 (版本 ≥ 3.8)
-
NumPy
-
TensorFlow(版本 ≥ 2.9.1)
-
TensorFlow Hub (
pipinstall tensorflow-hub) -
TensorFlow Datasets (
pipinstall tensorflow-datasets) -
TensorFlow Text (
pipinstall tensorflow-text)
由于模型的大小,建议使用带有适当 TensorFlow 安装的 GPU 来节省 NLP 示例的训练时间。
训练非-IID(非独立同分布)计算机视觉示例的代码实现需要以下库来运行:
-
Python 3(版本 ≥ 3.8)
-
NumPy
-
PyTorch(版本 ≥ 1.9)
-
Torchvision(版本 ≥ 0.10.0,与 PyTorch 版本相关联)
每个 FL 框架的安装说明列在以下子节中。
TensorFlow Federated
你可以安装以下库来使用 TFF:
-
tensorflow_federated(使用pip install tensorflow_federated命令) -
nest_asyncio(使用pip install nest_asyncio命令)
OpenFL
你可以使用pip install openfl命令安装 OpenFL。
或者,你可以使用以下命令从源代码构建:
git clone https://github.com/intel/openfl.git
cd openfl
pip install .
IBM FL
安装 IBM FL 的本地版本需要位于代码仓库中的 wheel 安装文件。要执行此安装,请运行以下命令:
git clone https://github.com/IBM/federated-learning-lib.git
cd federated-learning-lib
pip install federated_learning_lib-*-py3-none-any.whl
Flower
你可以使用pip install flwr命令安装 Flower。
STADLE
您可以使用 pip install stadle-client 命令安装 STADLE 客户端库。
联邦学习框架简介
首先,我们介绍后续实现重点章节中将要使用的联邦学习框架和平台。
Flower
Flower (flower.dev/) 是一个开源且与机器学习框架无关的联邦学习框架,旨在让用户易于使用。Flower 采用标准的客户端-服务器架构,其中客户端被设置为从服务器接收模型参数,在本地数据上训练,并将新的本地模型参数发送回服务器。
联邦学习过程的高级编排由 Flower 所称的策略决定,服务器使用这些策略来处理客户端选择和参数聚合等方面。
Flower 使用 远程过程调用 (RPCs) 来通过客户端执行从服务器发送的消息以执行所述编排。框架的可扩展性允许研究人员尝试新的方法,例如新的聚合算法和通信方法(如模型压缩)。
TensorFlow Federated (TFF)
TFF (www.tensorflow.org/federated) 是一个基于 TensorFlow 的开源 FL/计算框架,旨在允许研究人员轻松地使用现有的 TensorFlow/Keras 模型和训练管道模拟联邦学习。它包括联邦核心层,允许实现通用联邦计算,以及联邦学习层,它建立在核心之上,并为 FL 特定过程提供接口。
TFF 专注于 FL 的单机本地模拟,使用包装器从标准的 TensorFlow 等价物创建 TFF 特定的数据集、模型和联邦计算(FL 过程中执行的核心客户端和服务器计算)。从通用联邦计算构建一切的关注使研究人员能够按需实现每个步骤,从而支持实验。
OpenFL
OpenFL (github.com/intel/openfl) 是英特尔开发的开源联邦学习框架,专注于允许跨隔离区隐私保护机器学习。OpenFL 允许根据联盟(指整个 FL 系统)的预期生命周期选择两种不同的工作流程。
在基于聚合器的流程中,单个实验及其相关的联邦学习计划从聚合器发送到参与 协作者(代理)以作为 FL 流程的本地训练步骤运行——实验完成后,联盟停止。在基于导演的流程中,使用持久组件而不是短生命周期的组件,以便按需运行实验。以下图表描述了基于导演的流程的架构和用户:

图 8.1 – 基于总监的工作流程架构(改编自 https://openfl.readthedocs.io/en/latest/source/openfl/components.html)
总监经理负责实验的运行,与位于协作节点上的长期信使组件合作,管理每个实验的短期组件(协作者+聚合者)。在针对跨数据孤岛的场景时,OpenFL 对管理数据分片给予了独特的关注,包括数据表示在不同孤岛中不同的情况。
IBM FL
IBM FL 是一个也专注于企业联邦学习的框架。它遵循简单的聚合者-参与者设计,其中一些拥有本地数据的参与者通过向聚合者发送增量模型训练结果并与生成的聚合模型(遵循标准的客户端-服务器联邦学习架构)合作,与其他参与者协作。IBM FL 对多种融合(聚合)算法和旨在对抗偏见的某些公平技术提供官方支持——这些算法的详细信息可以在位于 https://github.com/IBM/federated-learning-lib 的存储库中找到。IBM FL 的一个具体目标是高度可扩展,使用户能够轻松地进行必要的修改,以满足特定的功能需求。它还支持基于 Jupyter-Notebook 的仪表板,以帮助协调联邦学习实验。
STADLE
与之前的框架不同,STADLE (stadle.ai/) 是一个与机器学习框架无关的联邦学习和分布式学习 SaaS 平台,旨在允许无缝地将联邦学习集成到生产就绪的应用程序和机器学习管道中。STADLE 的目标是最大限度地减少集成所需的特定于联邦学习的代码量,使联邦学习对新手来说易于访问,同时仍然为那些想要进行实验的人提供灵活性。
使用 STADLE SaaS 平台,不同技术能力的用户可以在所有规模上协作进行联邦学习项目。性能跟踪和模型管理功能使用户能够生成具有强大性能的验证联邦模型,而直观的配置面板允许对联邦学习过程进行详细控制。STADLE 使用两级组件层次结构,允许多个聚合器并行操作,以匹配需求。以下图展示了高级架构:

图 8.2 – STADLE 多聚合器架构
STADLE 客户端的开发通过pip安装和易于理解的配置文件简化,同时公开提供了一些示例,供用户参考 STADLE 如何集成到现有的机器学习代码中的不同方式。
PySyft
尽管由于代码库的持续变化,PySyft (github.com/OpenMined/PySyft) 的实现不包括在本章中,但它仍然是隐私保护深度学习空间中的主要参与者。PySyft 背后的核心原则是允许在不对数据进行直接访问的情况下对存储在机器上的数据进行计算。这是通过在用户和数据位置之间添加一个中间层来实现的,该层向参与工作的机器发送计算请求,将计算结果返回给用户,同时保持每个工人存储和使用的用于执行计算的数据的隐私。
这种通用能力直接扩展到 FL,重新设计正常深度学习训练流程的每一步,使其成为对每个参与 FL 的工人(代理)存储的模型参数和数据的计算。为了实现这一点,PySyft 利用钩子封装标准的 PyTorch/TensorFlow 库,修改必要的内部函数,以便支持模型训练和测试作为 PySyft 隐私保护计算。
现在已经解释了 FL 框架背后的高级思想,我们将转向其实际应用中的实现级细节,以两个示例场景为例。首先,我们来看如何修改现有的用于 NLP 模型的集中式训练代码,使其能够使用 FL。
示例 - NLP 模型的联邦训练
通过上述每个 FL 框架将第一个 ML 问题转换为 FL 场景的将是 NLP 领域的分类问题。从高层次来看,NLP 是指计算语言学和 ML 的交集,其总体目标是使计算机能够从人类语言中达到某种程度的理解 - 这种理解的细节根据要解决的具体问题而大相径庭。
在这个例子中,我们将对电影评论进行情感分析,将它们分类为正面或负面。我们将使用的数据集是 SST-2 数据集 (https://nlp.stanford.edu/sentiment/),包含以字符串格式表示的电影评论和相关的二进制标签 0/1,分别代表负面和正面情感。
我们将使用进行二元分类的模型是一个带有自定义分类头的预训练 BERT 模型。BERT 模型允许我们将句子编码成一个高维数值向量,然后将其传递到分类头以输出二元标签预测;有关 BERT 模型的更多信息,请参阅 https://huggingface.co/blog/bert-101。我们选择使用一个预训练模型,该模型在大量训练后已经学会了如何生成句子的通用编码,而不是从头开始进行训练。这允许我们将训练集中在分类头上,以微调模型在 SST-2 数据集上的性能,从而节省时间并保持性能。
现在,我们将通过本地(集中式)训练代码,该代码将作为展示如何使用每个 FL 框架的基础,从 Keras 模型定义和数据集加载器开始。
定义情感分析模型
在sst_model.py文件中定义的SSTModel对象是我们将在这个示例中使用的 Keras 模型。
首先,我们导入必要的库:
import tensorflow as tf
from tensorflow import keras
from keras import layers
import tensorflow_text
import tensorflow_hub as hub
import tensorflow_datasets as tfds
TensorFlow Hub 用于轻松下载预训练的 BERT 权重到 Keras 层。当从 TensorFlow Hub 加载 BERT 权重时使用 TensorFlow Text。TensorFlow Datasets 将允许我们下载和缓存 SST-2 数据集。
接下来,我们定义模型并初始化模型层对象:
class SSTModel(keras.Model):
def __init__(self):
super(SSTModel, self).__init__()
self.preprocessor = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3")
self.small_bert = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/4")
self.small_bert.trainable = False
self.fc1 = layers.Dense(512, activation='relu')
self.fc2 = layers.Dense(64, activation='relu')
self.fc3 = layers.Dense(1, activation='sigmoid')
preprocessor对象将原始句子输入批次转换为 BERT 模型使用的格式。我们从 TensorFlow Hub 加载预处理器和 BERT 层,然后初始化构成分类头的密集层。我们使用 sigmoid 激活函数在最后将输出压缩到区间(0,1),以便与真实标签进行比较。
然后,我们可以定义模型的正向传递:
def call(self, inputs):
input_dict = self.preprocessor(inputs)
bert_output = self.small_bert(input_dict)['pooled_output']
output = self.fc1(keras.activations.relu(bert_output, alpha=0.2))
scores = self.fc3(self.fc2(output))
return scores
我们将 leaky ReLU 应用于 BERT 输出,在传递到分类头层之前添加非线性。
创建数据加载器
我们还实现了一个函数,使用 TensorFlow Datasets 库加载 SST-2 数据集。首先,加载训练数据并将其转换为 NumPy 数组,以便在训练期间使用:
def load_sst_data(client_idx=None, num_clients=1):
x_train = []
y_train = []
for d in tfds.load(name="glue/sst2", split="train"):
x_train.append(d['sentence'].numpy())
y_train.append(d['label'].numpy())
x_train = np.array(x_train)
y_train = np.array(y_train)
我们以类似的方式加载测试数据:
x_test = []
y_test = []
for d in tfds.load(name="glue/sst2", split="validation"):
x_test.append(d['sentence'].numpy())
y_test.append(d['label'].numpy())
x_test = np.array(x_test)
y_test = np.array(y_test)
如果指定了client_idx和num_clients,我们返回训练数据集的相应分区——这将用于执行联邦学习:
if (client_idx is not None):
shard_size = int(x_train.size / num_clients)
x_train = x_train[client_idx*shard_size:(client_idx+1)*shard_size]
y_train = x_train[client_idx*shard_size:(client_idx+1)*shard_size]
return (x_train, y_train), (x_test, y_test)
接下来,我们检查位于local_training.py中的执行本地训练的代码。
训练模型
我们首先导入必要的库:
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel, load_sst_data
然后,我们可以使用之前定义的数据集加载器(不进行拆分)来加载训练和测试分割:
(x_train,y_train), (x_test,y_test) = load_sst_data()
现在,我们可以编译模型并开始训练:
model.compile(
optimizer = keras.optimizers.Adam(learning_rate=0.0005, amsgrad=False),
loss = keras.losses.BinaryCrossentropy(),
metrics = [keras.metrics.BinaryAccuracy()]
)
model.fit(x_train, y_train, batch_size=64, epochs=3)
最后,我们在测试分割上评估模型:
_, acc = model.evaluate(x_test, y_test, batch_size=64)
print(f"Accuracy of model on test set: {(100*acc):.2f}%")
经过三个训练周期后,模型应该达到大约 82%的测试准确率。
现在我们已经通过了本地训练代码,我们可以检查如何修改代码以使用上述每个 FL 框架进行联邦学习。
采用 FL 训练方法
为了展示如何将 FL 应用于 SST 模型训练场景,我们首先需要将原始 SST-2 数据集拆分成不相交的子集,这些子集代表 FL 应用中的本地数据集。为了简化问题,我们将研究三个代理各自在数据集的不同三分之一上训练的情况。
目前,这些子集是从数据集中随机采样而不重复的 – 在下一节“在非-IID 数据上对图像分类模型进行联邦训练”中,我们将研究本地数据集是从原始数据集的有偏采样中创建的情况。我们不会在本地训练三个 epoch,而是将进行三轮 FL,每轮本地训练阶段在本地数据上训练一个 epoch。FedAvg 将在每一轮结束时用于聚合本地训练的模型。在这三轮之后,将使用最终的聚合模型计算上述验证指标,从而允许比较本地训练案例和 FL 案例。
集成 TensorFlow Federated 用于 SST-2
如前所述,TensorFlow Federated(TFF)框架是在 TensorFlow 和 Keras 深度学习库之上构建的。模型实现是使用 Keras 完成的;因此,将 TFF 集成到本地训练代码中相对简单。
第一步是在加载数据集之前添加 TFF 特定的导入和 FL 特定的参数:
import nest_asyncio
nest_asyncio.apply()
import tensorflow_federated as tff
NUM_CLIENTS = 3
NUM_ROUNDS = 3
TFF 允许我们通过向 FL 过程传递适当数量的数据集(本地数据集)来模拟一定数量的代理。为了在预处理后将 SST-2 数据集分成三份,我们可以使用以下代码:
client_datasets = [load_sst_data(idx, NUM_CLIENTS)[0] for idx in range(NUM_CLIENTS)]
接下来,我们必须使用 TFF API 函数包装 Keras 模型,以便轻松创建相应的tff.learning.Model对象。我们创建一个函数,初始化 SST 模型,并将其与输入规范(关于每个数据元素大小的信息)一起传递给这个 API 函数,返回结果 – TFF 将在 FL 过程中内部使用此函数来创建模型:
def sst_model_fn():
sst_model = SSTModel()
sst_model.build(input_shape=(None,64))
return tff.learning.from_keras_model(
sst_model,
input_spec=tf.TensorSpec(shape=(None), dtype=tf.string),
loss=keras.metrics.BinaryCrossentropy()
)
使用sst_model_fn函数以及用于更新本地模型和聚合模型的优化器,可以创建 TFF FedAvg 过程。对于服务器优化器函数使用 1.0 的学习率,允许在每一轮结束时用新的聚合模型替换旧的模型(而不是计算旧模型和新模型的加权平均值):
fed_avg_process = tff.learning.algorithms.build_unweighted_fed_avg(
model_fn = sst_model_fn,
client_optimizer_fn = lambda: keras.optimizers.Adam(learning_rate=0.001),
server_optimizer_fn = lambda: keras.optimizers.SGD(learning_rate=1.0)
)
最后,我们初始化并运行联邦学习过程 10 轮。每次fed_avg_process.next()调用通过在客户端数据集上使用三个模型进行本地训练,然后使用 FedAvg 进行聚合来模拟一轮。第一轮后的状态被传递到下一次调用,作为该轮的起始 FL 状态:
state = fed_avg_process.initialize()
for round in range(NUM_ROUNDS):
state = fed_avg_process.next(state, client_datasets).state
FL 过程完成后,我们将最终的聚合 tff.learning.Model 对象转换回原始的 Keras 模型格式,以便计算验证指标:
fed_weights = fed_avg_process.get_model_weights(state)
fed_sst_model = SSTModel()
fed_sst_model.build(input_shape=(None, 64))
fed_sst_model.compile(
optimizer = keras.optimizers.Adam(learning_rate=0.005, amsgrad=False),
loss = keras.losses.BinaryCrossentropy(),
metrics = [keras.metrics.BinaryAccuracy()]
)
fed_weights.assign_weights_to(fed_sst_model)
_, (x_test, y_test) = load_sst_data()
_, acc = fed_sst_model.evaluate(x_test, y_test, batch_size=64)
print(f"Accuracy of federated model on test set: {(100*acc):.2f}%")
聚合模型的最终准确率应约为 82%。
从这一点来看,应该很清楚 TFF FedAvg 的结果几乎与本地训练场景的结果相同。
集成 OpenFL 用于 SST-2
请记住,OpenFL 支持两种不同的工作流程:基于聚合器的工作流程和基于导演的工作流程。本例将使用基于导演的工作流程,涉及长期存在的组件,可以处理传入的 FL 任务请求。这选择是因为希望有一个持久的 FL 设置来部署多个项目;然而,两种工作流程都执行相同的核心 FL 过程,因此表现出类似的表现。
为了帮助在此情况下进行模型序列化,我们只聚合分类头权重,在训练和验证时运行时重建完整模型(TensorFlow Hub 缓存下载的层,因此下载过程只发生一次)。我们在 sst_model.py 中包含以下函数以帮助进行此修改:
def get_sst_full(preprocessor, bert, classification_head):
sst_input = keras.Input(shape=(), batch_size=64, dtype=tf.string)
scores = classification_head(bert(preprocessor(sst_input))['pooled_output'])
return keras.Model(inputs=sst_input, outputs=scores, name='sst_model')
def get_classification_head():
classification_head = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=(768,)),
layers.Dense(64, activation='relu', input_shape=(512,)),
layers.Dense(1, activation='sigmoid', input_shape=(64,))
])
return classification_head
由于 OpenFL 专注于解决数据孤岛问题,从 SST-2 数据创建本地数据集比 TFF 情况稍微复杂一些。创建数据集所需的对象将在名为 sst_fl_dataset.py 的单独文件中实现。
首先,我们包括必要的导入。我们导入的两个 OpenFL 特定对象是处理数据集加载和分片的 ShardDescriptor 对象,以及处理数据集访问的 DataInterface 对象:
from openfl.interface.interactive_api.shard_descriptor import ShardDescriptor
from openfl.interface.interactive_api.experiment import DataInterface
import tensorflow as tf
from sst_model import load_sst_data
实现 ShardDescriptor
我们首先实现了 SSTShardDescriptor 类。当创建此分片描述符时,我们保存 rank(客户端编号)和 worldsize(客户端总数)值,然后加载训练和验证数据集:
class SSTShardDescriptor(ShardDescriptor):
def __init__(
self,
rank_worldsize: str = '1, 1',
**kwargs
):
self.rank, self.worldsize = tuple(int(num) for num in rank_worldsize.split(','))
(x_train,y_train), (x_test,y_test) = load_sst_data(self.rank-1, self.worldsize)
self.data_by_type = {
'train': tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(64),
'val': tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(64)
}
我们实现了 ShardDescriptor 类函数以获取可用的数据集类型(在这种情况下为训练和验证)以及基于客户端排名的相应数据集/分片:
def get_shard_dataset_types(self):
return list(self.data_by_type)
def get_dataset(self, dataset_type='train'):
if dataset_type not in self.data_by_type:
raise Exception(f'Wrong dataset type: {dataset_type}')
return self.data_by_type[dataset_type]
我们还指定了正在使用的数据集的具体属性。请注意,样本形状设置为 1。SSTModel 的预处理层允许我们传入字符串作为输入,这些字符串被视为类型为 tf.string 且长度为 1 的输入向量:
@property
def sample_shape(self):
return ["1"]
@property
def target_shape(self):
return ["1"]
@property
def dataset_description(self) -> str:
return (f'SST dataset, shard number {self.rank}'
f' out of {self.worldsize}')
这样,SSTShardDescriptor 的实现就完成了。
实现数据接口
接下来,我们将 SSTFedDataset 类实现为 DataInterface 的子类。这是通过实现分片描述符获取器和设置器方法来完成的,设置器方法准备要提供给训练/验证 FL 任务的数据:
class SSTFedDataset(DataInterface):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@property
def shard_descriptor(self):
return self._shard_descriptor
@shard_descriptor.setter
def shard_descriptor(self, shard_descriptor):
self._shard_descriptor = shard_descriptor
self.train_set = shard_descriptor.get_dataset('train')
self.valid_set = shard_descriptor.get_dataset('val')
我们还实现了 API 函数以授予数据集访问和数据集大小信息(用于聚合):
def get_train_loader(self):
return self.train_set
def get_valid_loader(self):
return self.valid_set
def get_train_data_size(self):
return len(self.train_set) * 64
def get_valid_data_size(self):
return len(self.valid_set) * 64
这样,就可以构建并使用本地 SST-2 数据集了。
创建 FLExperiment
现在,我们专注于在新的文件fl_sim.py中实现 FL 过程的实际实现。首先,我们导入必要的库——从 OpenFL 中,我们导入以下内容:
-
TaskInterface:允许我们为模型定义 FL 训练和验证任务;注册的任务是 director 指示每个 envoy 执行的任务 -
ModelInterface:允许我们将我们的 Keras 模型转换为 OpenFL 在注册任务中使用的格式 -
Federation:管理与 director 连接相关的信息 -
FLExperiment:使用TaskInterface、ModelInterface和Federation对象来执行 FL 过程
必要的导入如下所示:
import tensorflow as tf
from tensorflow import keras
import tensorflow_hub as hub
from openfl.interface.interactive_api.experiment import TaskInterface
from openfl.interface.interactive_api.experiment import ModelInterface
from openfl.interface.interactive_api.experiment import FLExperiment
from openfl.interface.interactive_api.federation import Federation
from sst_model import get_classification_head, get_sst_full
from sst_fl_dataset import SSTFedDataset
接下来,我们使用默认的director连接信息创建Federation对象:
client_id = 'api'
director_node_fqdn = 'localhost'
director_port = 50051
federation = Federation(
client_id=client_id,
director_node_fqdn=director_node_fqdn,
director_port=director_port,
tls=False
)
然后,我们使用相关的优化器和损失函数初始化模型——这些对象被 OpenFL 的KerasAdapter用于创建ModelInterface对象。我们在一个虚拟的 Keras 输入上调用模型,以便在将模型传递给ModelInterface之前初始化所有权重:
classification_head = get_classification_head()
optimizer = keras.optimizers.Adam(learning_rate=0.005, amsgrad=False)
loss = keras.losses.BinaryCrossentropy()
framework_adapter = 'openfl.plugins.frameworks_adapters.keras_adapter.FrameworkAdapterPlugin'
MI = ModelInterface(model=classification_head, optimizer=optimizer, framework_plugin=framework_adapter)
接下来,我们创建一个TaskInterface对象,并使用它来注册训练任务。请注意,将优化器包含在任务的装饰器函数中会导致训练数据集被传递给任务;否则,验证数据集将被传递给任务:
TI = TaskInterface()
@TI.register_fl_task(model='model', data_loader='train_data', device='device', optimizer='optimizer')
def train(model, train_data, optimizer, device):
preprocessor = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3")
small_bert = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/4")
small_bert.trainable = False
full_model = get_sst_full(preprocessor, small_bert, model)
full_model.compile(loss=loss, optimizer=optimizer)
history = full_model.fit(train_data, epochs=1)
return {'train_loss':history.history['loss'][0]}
类似地,我们使用TaskInterface对象注册验证任务。请注意,我们可以收集由evaluate函数生成的指标,并将值作为跟踪性能的手段:
@TI.register_fl_task(model='model', data_loader='val_data', device='device')
def validate(model, val_data, device):
preprocessor = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3")
small_bert = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/4")
small_bert.trainable = False
full_model = get_sst_full(preprocessor, small_bert, model)
full_model.compile(loss=loss, optimizer=optimizer)
loss, acc = full_model.evaluate(val_data, batch_size=64)
return {'val_acc':acc, 'val_loss':loss,}
现在,我们可以使用之前实现的SSTFedDataset类加载数据集,并使用创建的ModelInterface、TaskInterface和SSTFedDatasets对象创建并启动一个新的FLExperiment:
fed_dataset = SSTFedDataset()
fl_experiment = FLExperiment(federation=federation, experiment_name='sst_experiment')
fl_experiment.start(
model_provider=MI,
task_keeper=TI,
data_loader=fed_dataset,
rounds_to_train=3,
opt_treatment='CONTINUE_LOCAL'
)
定义配置文件
最后一步是创建由director和envoys使用的配置文件,以便实际加载数据并启动 FL 过程。首先,我们创建包含以下信息的director_config:
settings:
listen_host: localhost
listen_port: 50051
sample_shape: ["1"]
target_shape: ["1"]
这被保存在director/director_config.yaml中。
我们随后创建了三个envoy配置文件。第一个文件(envoy_config_1.yaml)包含以下内容:
params:
cuda_devices: []
optional_plugin_components: {}
shard_descriptor:
template: sst_fl_dataset.SSTShardDescriptor
params:
rank_worldsize: 1, 3
第二个和第三个envoy配置文件与第一个相同,只是rank_worldsize的值分别为2, 3和3, 3。这些配置文件以及所有代码文件都存储在实验目录中。目录结构应如下所示:
-
directordirector_config.yaml
-
experiment-
envoy_config_1.yaml -
envoy_config_2.yaml -
envoy_config_3.yaml -
sst_fl_dataset.py -
sst_model.py -
fl_sim.py(包含FLExperiment创建的文件)
-
一切准备就绪后,我们现在可以使用 OpenFL 执行 FL。
运行 OpenFL 示例
首先,从director文件夹中运行以下命令以启动 director(确保 OpenFL 已安装在工作环境中):
fx director start --disable-tls -c director_config.yaml
接下来,在实验目录中分别在不同的终端运行以下命令:
fx envoy start -n envoy_1 -–disable-tls --envoy-config-path envoy_config_1.yaml -dh localhost -dp 50051
fx envoy start -n envoy_2 -–disable-tls --envoy-config-path envoy_config_2.yaml -dh localhost -dp 50051
fx envoy start -n envoy_3 -–disable-tls --envoy-config-path envoy_config_3.yaml -dh localhost -dp 50051
最后,通过运行 fl_sim.py 脚本来启动 FLExperiment。完成三轮后,聚合模型应该达到大约 82% 的验证准确率。再次强调,性能几乎与本地训练场景相同。
集成 IBM FL 用于 SST-2
IBM FL 在执行联邦学习时使用保存的模型版本。以下代码(create_saved_model.py)初始化一个模型(在虚拟输入上调用模型以初始化参数)然后以 Keras SavedModel 格式保存模型供 IBM FL 使用:
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel
sst_model = SSTModel()
optimizer = keras.optimizers.Adam(learning_rate=0.005, amsgrad=False)
loss = keras.losses.BinaryCrossentropy(),
sst_model.compile(loss=loss, optimizer=optimizer)
sst_input = keras.Input(shape=(), dtype=tf.string)
sst_model(sst_input)
sst_model.save('sst_model_save_dir')
运行此命令一次以将模型保存到名为 sst_model_save_dir 的文件夹中 – 我们将指示 IBM FL 从此目录加载保存的模型。
创建 DataHandler
接下来,我们创建一个 IBM FL DataHandler 类的子类,该类负责向模型提供训练和验证数据 – 这个子类将加载、预处理并存储 SST 数据集作为类属性。我们首先导入必要的库:
from ibmfl.data.data_handler import DataHandler
import tensorflow as tf
from sst_model import load_sst_data
这个类的 init 函数加载数据信息参数,然后使用这些参数来加载正确的数据集分片 SST-2:
class SSTDataHandler(DataHandler):
def __init__(self, data_config=None):
super().__init__()
if (data_config is not None):
if ('client_id' in data_config):
self.client_id = int(data_config['client_id'])
if ('num_clients' in data_config):
self.num_clients = int(data_config['num_clients'])
train_data, val_data = load_sst_data(self.client_id-1, self.num_clients)
self.train_dataset = tf.data.Dataset.from_tensor_slices(train_data).batch(64)
self.val_dataset = tf.data.Dataset.from_tensor_slices(val_data).batch(64)
我们还实现了返回用于训练/验证期间使用的加载数据集的 API 函数:
def get_data(self):
return self.train_dataset, self.val_dataset
定义配置文件
下一步是创建在启动聚合器和初始化聚会时使用的配置 JSON 文件。聚合配置首先指定它将用于与聚会通信的连接信息:
{
"connection": {
"info": {
"ip": "127.0.0.1",
"port": 5000,
"tls_config": {
"enable": "false"
}
},
"name": "FlaskConnection",
"path": "ibmfl.connection.flask_connection",
"sync": "False"
},
接下来,我们指定用于聚合的融合处理器:
"fusion": {
"name": "IterAvgFusionHandler",
"path": "ibmfl.aggregator.fusion.iter_avg_fusion_handler"
},
我们还指定了与本地训练和聚合相关的超参数。perc_quorum 指的是在聚合开始之前必须参与聚会的比例:
"hyperparams": {
"global": {
"max_timeout": 10800,
"num_parties": 1,
"perc_quorum": 1,
"rounds": 3
},
"local": {
"optimizer": {
"lr": 0.0005
},
"training": {
"epochs": 1
}
}
},
最后,我们指定要使用的 IBM FL 协议处理器:
"protocol_handler": {
"name": "ProtoHandler",
"path": "ibmfl.aggregator.protohandler.proto_handler"
}
}
此配置保存在 agg_config.json 文件中。
我们还创建了用于使用本地数据进行联邦学习的基聚会配置文件。我们首先指定聚合器和聚会的连接信息:
{
"aggregator":
{
"ip": "127.0.0.1",
"port": 5000
},
"connection": {
"info": {
"ip": "127.0.0.1",
"port": 8085,
"id": "party",
"tls_config": {
"enable": "false"
}
},
"name": "FlaskConnection",
"path": "ibmfl.connection.flask_connection",
"sync": "false"
},
然后,我们指定要使用的数据处理器和本地训练处理器 – 此组件使用模型信息和本地数据训练 SST 模型:
"data": {
"info": {
"client_id": 0,
"num_clients": 3
},
"name": "SSTDataHandler",
"path": "sst_data_handler"
},
"local_training": {
"name": "LocalTrainingHandler",
"path": "ibmfl.party.training.local_training_handler"
},
指定模型格式和信息 – 这是我们指向之前创建的保存模型的地方:
"model": {
"name": "TensorFlowFLModel",
"path": "ibmfl.model.tensorflow_fl_model",
"spec": {
"model-name": "sst_model",
"model_definition": "sst_model_save_dir"
}
},
最后,我们指定协议处理器:
"protocol_handler": {
"name": "PartyProtocolHandler",
"path": "ibmfl.party.party_protocol_handler"
}
}
创建 IBM FL 聚会
使用这种方式,剩下的只是启动每个聚会的代码,保存在 fl_sim.py 文件中。我们首先导入必要的库:
import argparse
import json
from ibmfl.party.party import Party
我们包含一个 argparse 参数,允许指定聚会编号 – 这用于修改基本聚会配置文件,以便从同一文件启动不同的聚会:
parser = argparse.ArgumentParser()
parser.add_argument("party_id", type=int)
args = parser.parse_args()
party_id = args.party_id
with open('party_config.json') as cfg_file:
party_config = json.load(cfg_file)
party_config['connection']['info']['port'] += party_id
party_config['connection']['info']['id'] += f'_{party_id}'
party_config['data']['info']['client_id'] = party_id
最后,我们使用修改后的配置信息创建并启动一个新的 Party 对象:
party = Party(config_dict=party_config)
party.start()
party.register_party()
使用这种方式,我们现在可以开始使用 IBM FL 进行联邦学习。
运行 IBM FL 示例
首先,通过运行以下命令来启动 aggregator:
python -m ibmfl.aggregator.aggregator agg_config.json
在聚合器完成设置后,输入 START 并按 Enter 键以打开聚合器以接收传入的连接。然后,你可以在单独的终端中使用以下命令启动三个参与者:
python fl_sim.py 1
python fl_sim.py 2
python fl_sim.py 3
最后,在聚合器窗口中输入 TRAIN 并按 Enter 键开始 FL 流程。当完成三轮后,你可以在同一窗口中输入 SAVE 以保存最新的聚合模型。
将 Flower 集成到 SST-2 中
必须在现有本地训练代码之上集成的两个主要 Flower 组件是客户端和策略子类实现。客户端子类实现允许我们与 Flower 接口,API 函数允许在客户端和服务器之间传递模型参数。策略子类实现允许我们指定服务器执行的聚合方法的细节。
我们首先编写代码来实现并启动客户端(存储在 fl_sim.py 中)。首先,导入必要的库:
import argparse
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel, load_sst_data
import flwr as fl
我们添加一个命令行参数来指定客户端 ID,以便允许相同的客户端脚本被所有三个代理重用:
parser = argparse.ArgumentParser()
parser.add_argument("client_id", type=int)
args = parser.parse_args()
client_id = args.client_id
NUM_CLIENTS = 3
然后我们加载 SST-2 数据集:
(x_train,y_train), (x_test,y_test) = load_sst_data(client_id-1, NUM_CLIENTS)
注意,我们使用客户端 ID 从训练数据集中获取相应的分片。
接下来,我们创建模型和相关优化器以及损失对象,确保在哑输入上调用模型以初始化权重:
sst_model = SSTModel()
sst_model.compile(
optimizer = keras.optimizers.Adam(learning_rate=0.005, amsgrad=False),
loss = keras.losses.BinaryCrossentropy(),
metrics = [keras.metrics.BinaryAccuracy()]
)
sst_input = keras.Input(shape=(), dtype=tf.string)
sst_model(sst_input)
实现 Flower 客户端
现在我们可以实现 Flower 客户端对象,该对象将在服务器之间传递模型参数。要实现客户端子类,我们必须定义三个函数:
-
get_parameters(self, config): 返回模型参数值 -
fit(self, parameters, config): 将本地模型的权重设置为接收到的参数,执行本地训练,并返回新的模型参数以及数据集大小和训练指标 -
evaluate(self, parameters, config): 将本地模型的权重设置为接收到的参数,然后在验证/测试数据上评估模型,并返回性能指标
使用 fl.client.NumPyClient 作为超类允许我们利用 Keras 模型的 get_weights 和 set_weights 函数,这些函数将模型参数转换为 NumPy 数组的列表:
class SSTClient(fl.client.NumPyClient):
def get_parameters(self, config):
return sst_model.get_weights()
def fit(self, parameters, config):
sst_model.set_weights(parameters)
history = sst_model.fit(x_train, y_train, epochs=1)
return sst_model.get_weights(), len(x_train), {'train_loss':history.history['loss'][0]}
evaluate 函数也被定义:
def evaluate(self, parameters, config):
sst_model.set_weights(parameters)
loss, acc = sst_model.evaluate(x_test, y_test, batch_size=64)
return loss, len(x_train), {'val_acc':acc, 'val_loss':loss}
使用此客户端实现,我们最终可以使用以下行使用默认连接信息启动客户端:
fl.client.start_numpy_client(server_address="[::]:8080", client=SSTClient())
创建 Flower 服务器
在运行 Flower 之前,我们需要创建一个脚本(server.py),该脚本将启动 Flower 服务器。我们开始导入必要的库和 MAX_ROUNDS 参数:
import flwr as fl
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel
MAX_ROUNDS = 3
因为我们希望在执行联邦学习后保存模型,所以我们创建了一个 flower FedAvg 策略的子类,并在聚合阶段的最后一步添加了一个保存模型的步骤:
class SaveKerasModelStrategy(fl.server.strategy.FedAvg):
def aggregate_fit(self, server_round, results, failures):
agg_weights = super().aggregate_fit(server_round, results, failures)
if (server_round == MAX_ROUNDS):
sst_model = SSTModel()
sst_input = keras.Input(shape=(), dtype=tf.string)
sst_model(sst_input)
sst_model.set_weights(fl.common.parameters_to_ndarrays(agg_weights[0]))
sst_model.save('final_agg_sst_model')
return agg_weights
使用这种策略,我们可以运行以下行来启动服务器(通过 config 参数传递 MAX_ROUNDS 参数):
fl.server.start_server(strategy=SaveKerasModelStrategy(), config=fl.server.ServerConfig(num_rounds=MAX_ROUNDS))
现在我们可以启动服务器和客户端,允许使用 Flower 进行 FL。
运行 Flower 示例
要启动服务器,首先运行 server.py 脚本。
每个客户端都可以通过在单独的终端窗口中运行以下命令来启动:
python fl_sim.py 1
python fl_sim.py 2
python fl_sim.py 3
FL 最终的聚合模型将被保存在 final_agg_sst_model 目录中,作为一个 SavedModel 对象。
集成 STADLE 用于 SST-2
STADLE 与之前考察的 FL 框架不同,它提供了一个基于云的平台(STADLE Ops),用于处理聚合器的部署和 FL 流程的管理。因为服务器端的部署可以通过该平台完成,所以使用 STADLE 进行 FL 所需实现的只是客户端的实现。这种集成是通过创建一个客户端对象来完成的,该对象偶尔发送本地模型,并从上一轮返回聚合模型。为此,我们需要创建代理配置文件,并修改本地训练代码以与 STADLE 接口。
首先,我们创建代理的配置文件,如下所示:
{
"model_path": "./data/agent",
"aggr_ip": "localhost",
"reg_port": "8765",
"token": "stadle12345",
"base_model": {
"model_fn": "SSTModel",
"model_fn_src": "sst_model",
"model_format": "Keras",
"model_name": "Keras-SST-Model"
}
}
这些参数的详细信息可以在 https://stadle-documentation.readthedocs.io/en/latest/documentation.html#configuration-of-agent 找到。请注意,这里列出的聚合器 IP 和注册端口号是占位符,在连接到 STADLE Ops 平台时将被修改。
接下来,我们修改本地训练代码以与 STADLE 一起工作。我们首先导入所需的库:
import argparse
import tensorflow as tf
from tensorflow import keras
from sst_model import SSTModel, load_sst_data
from stadle import BasicClient
再次,我们添加一个命令行参数来指定代理应接收的训练数据分区:
parser = argparse.ArgumentParser()
parser.add_argument("client_id", type=int)
args = parser.parse_args()
client_id = args.client_id
NUM_CLIENTS = 3
(x_train,y_train), (x_test,y_test) = load_sst_data(client_id-1, NUM_CLIENTS)
接下来,我们实例化一个 BasicClient 对象——这是 STADLE 客户端组件,用于处理本地训练过程与服务器端聚合器之间的通信。我们使用之前定义的配置文件来创建此客户端:
stadle_client = BasicClient(config_file="config_agent.json", agent_name=f"sst_agent_{client_id}")
最后,我们实现 FL 训练循环。在每一轮中,客户端从上一轮(从基础模型开始)获取聚合模型,并在本地数据上进一步训练,然后再通过客户端将其发送回聚合器:
for round in range(3):
sst_model = stadle_client.wait_for_sg_model()
history = sst_model.fit(x_train, y_train, epochs=1)
loss = history.history['loss'][0]
stadle_client.send_trained_model(sst_model, {'loss_training': loss})
stadle_client.disconnect()
wait_for_sg_model 函数从服务器返回最新的聚合模型,而 send_trained_model 函数将具有所需性能指标的本地训练模型发送到服务器。有关这些集成步骤的更多信息,请参阅 https://stadle-documentation.readthedocs.io/en/latest/usage.html#client-side-stadle-integration。
现在客户端实现完成后,我们可以使用 STADLE Ops 平台启动一个聚合器并启动一个 FL 流程。
创建 STADLE Ops 项目
首先,访问 stadle.ai 并创建一个新账户。一旦登录,你应该会被引导到 STADLE Ops 的项目信息页面:
![图 8.3 – STADLE Ops 中的项目信息页面
![图片 B18369_08_03.jpg]
图 8.3 – STADLE Ops 中的项目信息页面
点击创建新项目,然后填写项目信息并点击创建项目。项目信息页面应已更改以显示以下内容:
![图 8.4 – 新项目添加到项目信息页面
![图片 B18369_08_04.jpg]
图 8.4 – 新项目添加到项目信息页面
点击启动聚合器下方的加号图标以启动项目的新聚合器,然后在确认提示中点击确定。现在您可以导航到左侧的仪表板页面,页面看起来如下所示:
![图 8.5 – STADLE Ops 仪表板页面
![图片 B18369_08_05.jpg]
图 8.5 – STADLE Ops 仪表板页面
将config_agent.json文件中的aggr_ip和reg_port占位符参数值分别替换为连接 IP 地址和连接端口下的值。
这样,我们现在就可以开始 FL 训练过程了。
运行 STADLE 示例
第一步是将基础模型对象发送到服务器,使其能够反过来将模型分发给训练代理。这可以通过以下命令完成:
stadle upload_model --config_path config_agent.json
一旦命令成功运行,STADLE Ops 仪表板上的基础模型信息部分应更新以显示模型信息。现在我们可以通过运行以下命令来启动三个代理:
python fl_sim.py 1
python fl_sim.py 2
python fl_sim.py 3
经过三轮后,代理将终止,最终的聚合模型将在项目仪表板上显示,并以 Keras SavedModel 格式可供下载。建议查阅位于stadle.ai/user_guide/guide的用户指南,以获取有关 STADLE Ops 平台各种功能的更多信息。
评估每个联邦学习框架产生的结果聚合模型,得出的结论相同——聚合模型的性能基本上与集中式训练模型的性能相匹配。正如在第七章的“数据集分布”部分所解释的,模型聚合,这通常是预期的结果。自然要问的是,当本地数据集不是独立同分布(IID)时,性能会受到怎样的影响——这是下一节的重点。
示例 – 在非 IID 数据上对图像分类模型进行联邦训练
在前面的例子中,我们考察了如何通过在联邦学习过程中在原始训练数据集(本地数据集)的不相交子集上训练多个客户端来将集中式深度学习问题转换为联邦学习的类似问题。这个本地数据集创建的一个关键点是,子集是通过随机采样创建的,导致所有本地数据集在原始数据集相同的分布下都是独立同分布的。因此,FedAvg 与本地训练场景相似的性能是可以预期的——每个客户端的模型在训练过程中本质上都有相同的局部最小值集合要移动,这使得所有本地训练都对全局目标有益。
回想一下,在第七章“模型聚合”中,我们探讨了 FedAvg 如何容易受到严重非独立同分布的本地数据集引起的训练目标发散的影响。为了探索 FedAvg 在变化非独立同分布严重程度上的性能,本例在从 CIFAR-10 数据集(位于www.cs.toronto.edu/~kriz/cifar.html)中采样的构建的非独立同分布的本地数据集上训练了 VGG-16 模型(一个基于简单深度学习的图像分类模型)。CIFAR-10 是一个著名的简单图像分类数据集,包含 60,000 张图像,分为 10 个不同的类别;在 CIFAR-10 上训练的模型的目标是正确预测与输入图像相关联的类别。相对较低复杂性和作为基准数据集的普遍性使 CIFAR-10 成为探索 FedAvg 对非独立同分布数据的响应的理想选择。
重要提示
为了避免包含冗余的代码示例,本节重点介绍允许在 PyTorch 模型上使用非独立同分布的本地数据集执行联邦学习的关键代码行。建议在阅读本节之前,先阅读本章中“示例 – NLP 模型的联邦训练”部分中的示例,以便了解每个联邦学习框架所需的核心组件。本例的实现可以在本书的 GitHub 仓库中找到,完整内容位于github.com/PacktPublishing/Federated-Learning-with-Python树/main/ch8/cv_code),供参考使用。
本例的关键点是确定如何构建非独立同分布(non-IID)数据集。我们将通过改变训练数据集中每个类别的图像数量来改变每个本地数据集的类别标签分布。例如,一个偏向于汽车和鸟类的数据集可能包含 5,000 张汽车的图像,5,000 张鸟类的图像,以及每个其他类别 500 张图像。通过创建 10 个类别的三个不相交子集,并构建偏向这些类别的本地数据集,我们产生了三个本地数据集,其非独立同分布的严重程度与从未选择的类别中包含的图像数量成比例。
偏斜 CIFAR-10 数据集
我们首先将三个类别子集映射到客户端 ID,并设置从原始数据集中选取的类别(sel_count)和其他类别(del_count)的图像比例:
classes = ('airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
class_id_map = {
1: classes[:3],
2: classes[3:6],
3: classes[6:]
}
sel_count = 1.0, def_count = 0.2
然后我们从原始数据集中采样适当数量的图像,使用数据集中图像的索引来构建有偏的 CIFAR-10 子集:
class_counts = int(def_count * 5000) * np.ones(len(classes))
for c in classes:
if c in class_rank_map[self.rank]:
class_counts[trainset.class_to_idx[c]] = int(sel_count * 5000)
class_counts_ref = np.copy(class_counts)
imbalanced_idx = []
for i,img in enumerate(trainset):
c = img[1]
if (class_counts[c] > 0):
imbalanced_idx.append(i)
class_counts[c] -= 1
trainset = torch.utils.data.Subset(trainset, imbalanced_idx)
然后使用有偏的训练集创建用于本地训练的有偏 trainloader。当我们提到对未来的训练数据进行偏差时,这就是运行的代码。
我们现在将演示如何使用不同的 FL 框架来运行这个非-IID FL 流程。请参阅上一节 示例 - NLP 模型的联邦训练 中的安装说明和框架特定实现,以了解本节中省略的基本概念。
集成 OpenFL 用于 CIFAR-10
与 Keras NLP 示例类似,我们首先在 cifar_fl_dataset.py 中为非-IID 的 CIFAR-10 数据集创建 ShardDescriptor 和 DataInterface 子类。为了适应新的数据集,只需要进行少数几个更改。
首先,我们修改 self.data_by_type 字典,以便存储修改后的 CIFAR 数据集:
train_dataset, val_dataset = self.load_cifar_data()
self.data_by_type = {
'train': train_dataset,
'val': val_dataset
}
load_cifar_data 函数使用 torchvision 加载训练和测试数据,然后根据传递给对象的排名对训练数据进行偏差。
由于数据元素的维度现在已知(CIFAR-10 图像的大小),我们还使用固定值修改了形状属性:
@property
def sample_shape(self):
return ["32", "32"]
@property
def target_shape(self):
return ["10"]
然后我们实现 CifarFedDataset 类,它是 DataInterface 类的子类。对于这个实现不需要进行重大修改;因此,我们现在可以使用带有 OpenFL 的有偏 CIFAR-10 数据集。
现在我们转向实际的 FL 流程实现 (fl_sim.py)。一个关键的区别是必须使用框架适配器来从 PyTorch 模型创建 ModelInterface 对象:
model = vgg16()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=5e-4)
criterion = nn.CrossEntropyLoss()
framework_adapter = 'openfl.plugins.frameworks_adapters.pytorch_adapter.FrameworkAdapterPlugin'
MI = ModelInterface(model=model, optimizer=optimizer, framework_plugin=framework_adapter)
唯一的另一个主要更改是修改传递给 TaskInterface 对象的培训和验证函数,以反映本地训练代码中这些函数的 PyTorch 实现。
最后一步是创建导演和使节使用的配置文件。导演配置中唯一必要的更改是更新 CIFAR-10 数据的 sample_shape 和 target_shape:
settings:
listen_host: localhost
listen_port: 50051
sample_shape: ["32","32"]
target_shape: ["10"]
这个文件保存在 director/director_config.yaml 中。
使节配置文件除了更新对象和文件名之外不需要任何更改——目录结构应该如下所示:
-
directordirector_config.yaml
-
experiment-
envoy_config_1.yaml -
envoy_config_2.yaml -
envoy_config_3.yaml -
cifar_fl_dataset.py -
fl_sim.py
-
您可以参考 *在 集成 OpenFL 用于 SST-2 部分的 运行 OpenFL 示例 来运行此示例。
集成 IBM FL 用于 CIFAR-10
记住,IBM FL 需要保存训练过程中使用的模型版本。我们首先在 create_saved_model.py 中运行以下代码以创建保存的 VGG-16 PyTorch 模型:
import torch
from torchvision.models import vgg16
model = vgg16()
torch.save(model, 'saved_vgg_model.pt')
接下来,我们为倾斜的 CIFAR-10 数据集创建DataHandler子类。唯一的核心更改是修改load_and_preprocess_data函数,以加载 CIFAR-10 数据并对训练集进行偏差。
下一步是创建启动聚合器和初始化各方时使用的配置 JSON 文件。聚合器配置(agg_config.json)无需进行重大更改,而各方配置的核心更改仅是修改模型信息以与 PyTorch 兼容:
"model": {
"name": "PytorchFLModel",
"path": "ibmfl.model.pytorch_fl_model",
"spec": {
"model-name": "vgg_model",
"model_definition": "saved_vgg_model.pt",
"optimizer": "optim.SGD",
"criterion": "nn.CrossEntropyLoss"
}
},
由于广泛使用配置文件,fl_sim.py中负责启动各方代码基本上无需修改。
您可以参考在 SST-2 中集成 IBM FL部分的运行 IBM FL 示例来运行此示例。
集成 Flower 用于 CIFAR-10
在加载 CIFAR-10 数据并对训练数据进行偏差后,Flower 实现所需的核心更改是NumPyClient子类。与 Keras 示例不同,get_parameters和set_parameters方法依赖于 PyTorch 模型状态字典,并且更为复杂:
class CifarClient(fl.client.NumPyClient):
def get_parameters(self, config):
return [val.numpy() for _, val in model.state_dict().items()]
def set_parameters(self, parameters):
params_dict = zip(model.state_dict().keys(), parameters)
state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict})
model.load_state_dict(state_dict)
我们修改fit函数,使其与本地训练示例中的训练代码相匹配,并修改evaluate函数,使其与本地训练评估代码相类似。请注意,我们调用self.set_parameters(parameters)来更新本地模型实例的最新权重。
我们还在启动 Flower 客户端和服务器时将grpc_max_message_length参数设置为 1 GB,以适应更大的 VGG16 模型大小。客户端初始化函数现在是以下内容:
fl.client.start_numpy_client(
server_address="[::]:8080",
client=CifarClient(),
grpc_max_message_length=1024**3
)
最后,我们修改了server.py中的聚合器代码——我们之前用于在最后一轮结束时保存聚合模型的自定义策略需要修改以与 PyTorch 模型兼容:
if (server_round == MAX_ROUNDS):
vgg_model = vgg16()
np_weights = fl.common.parameters_to_ndarrays(agg_weights[0])
params_dict = zip(vgg_model.state_dict().keys(), np_weights)
state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict})
torch.save(state_dict, "final_agg_vgg_model.pt")
使用此策略,我们可以运行以下行来启动服务器(在此处也添加了grpc_max_message_length参数):
fl.server.start_server(
strategy=SavePyTorchModelStrategy(),
config=fl.server.ServerConfig(num_rounds=MAX_ROUNDS),
grpc_max_message_length=1024**3
)
请参考在 SST-2 中集成 Flower部分的运行 Flower 示例来运行此示例。
集成 STADLE 用于 CIFAR-10
我们首先修改config_agent.json配置文件,以使用torchvision库中的 VGG16 模型:
{
"model_path": "./data/agent",
"aggr_ip": "localhost",
"reg_port": "8765",
"token": "stadle12345",
"base_model": {
"model_fn": "vgg16",
"model_fn_src": "torchvision.models",
"model_format": "PyTorch",
"model_name": "PyTorch-VGG-Model"
}
}
要将 STADLE 集成到本地训练代码中,我们初始化BasicClient对象,并修改训练循环,每两个本地训练轮次发送一次本地模型,并等待新的聚合模型:
stadle_client = BasicClient(config_file="config_agent.json")
for epoch in range(num_epochs):
state_dict = stadle_client.wait_for_sg_model().state_dict()
model.load_state_dict(state_dict)
# Normal training code...
if (epoch % 2 == 0):
stadle_client.send_trained_model(model)
注意
位于github.com/PacktPublishing/Federated-Learning-with-Python的代码包含此集成示例的完整实现,供参考。要启动聚合器并使用 CIFAR-10 STADLE 示例进行联邦学习,请参考创建 STADLE Ops 项目和在 SST-2 中运行 STADLE 示例部分。
测试构建的局部数据集中不同水平的偏差,应得出与第七章中“数据集分布”部分所陈述的相同结论——模型聚合对于非独立同分布情况——随着非独立同分布严重程度的增加,收敛速度和模型性能降低。本节的目标是在 SST-2 示例中理解每个联邦学习框架的基础上,突出与修改后的数据集上使用 PyTorch 模型所需的关键变化。结合本节和github.com/PacktPublishing/Federated-Learning-with-Python中的代码示例,应有助于理解此示例集成。
摘要
在本章中,我们通过两个不同示例的背景,介绍了几个联邦学习(FL)框架。从第一个示例中,你学习了如何通过将数据分割成互不重叠的子集,将传统的集中式机器学习(ML)问题转化为类似的联邦学习场景。现在很清楚,随机采样会导致局部数据集是独立同分布(IID),这使得 FedAvg 能够达到与集中式等效的任何联邦学习框架相同的性能水平。
在第二个示例中,你了解到了一组数据集可以是非独立同分布(不同类别标签分布)的许多方法之一,并观察到了不同严重程度的非独立同分布数据集如何影响 FedAvg 的性能。我们鼓励你探索如何通过替代聚合方法在这些情况下改进 FedAvg。
这两个示例也应该让你对不同联邦学习框架工作时的一般趋势有了坚实的理解;虽然具体的实现级细节可能会改变(由于该领域的快速变化),但核心概念和实现细节将仍然是基础。
在下一章中,我们将继续转向联邦学习的商业应用方面,通过研究涉及联邦学习在特定领域应用的几个案例研究。
第九章:联邦学习应用的关键用例案例研究
联邦学习(FL)迄今为止在各种情境下遇到了各种人工智能应用,并在这些领域通过试错探索了整合。最受欢迎的领域之一是医疗和保健领域,其中保护隐私的人工智能概念自然符合医疗保健人工智能的当前需求和挑战。FL 还应用于金融服务行业、边缘计算设备和物联网(IoT),通过这些应用,FL 在许多应用中显示出显著的益处,这将解决许多重要的社会问题。
在本章中,我们将讨论 FL 在不同领域的一些主要用例。我们希望到本章结束时,您将熟悉 FL 在不同行业中的应用。我们将从探索 FL 在医疗和金融行业中的应用开始,然后过渡到边缘计算和物联网领域。最后,我们将通过讨论 FL 与大数据分布式学习的交汇来结束本章。
本章将涵盖以下主题:
-
将 FL 应用于医疗保健行业
-
将 FL 应用于金融行业
-
FL 与边缘计算相遇
-
向智能互联网迈进
-
将 FL 应用于大数据分布式学习
将 FL 应用于医疗保健行业
在过去几年中,联邦学习(FL)在医疗保健领域的应用引起了相当多的关注。医疗保健的进步可以对我们生活产生巨大的影响。然而,一些挑战使得这些进步可能比其他领域更困难。让我们首先讨论一些存在的常见挑战,以及它们如何阻碍医疗保健领域人工智能的进一步发展。
医疗保健的挑战
其中一个主要挑战是数据可访问性。数据可访问性不是一个仅限于医疗保健的问题。它是整个人工智能行业的一个巨大问题,并且随着时间的推移,它将变得更加困难。它是医疗保健领域人工智能发展的核心问题,我们将在下面简要讨论为什么这是一个问题。我们还将继续回顾这个主要障碍,从许多不同的角度和应用中解决问题和解决方案。这种策略将使您了解问题的许多不同方面、复杂性和驱动因素。
数据可访问性问题有许多组成部分:
-
隐私法规:数据可访问性问题的主要瓶颈集中在为保护个人数据而实施的隐私法规上;这些法规绝对必要,并且应该得到实施。我们不会绕过这些法规,而是将讨论如何与它们并行工作,保持这些宝贵的法规不变,同时利用这些数据可以提供的智能。你可以将其视为两全其美的情景。在第一章的“数据隐私作为瓶颈”部分以及“大数据挑战”和“传统人工智能”中讨论了几个重要的隐私法规。
-
数据不足/需要真实数据:在提供积极社会影响方面,很少有领域像医疗保健那样具有如此大的潜力。然而,医疗保健行业在利用人工智能提供的所有许多好处方面远远落后。其中一个原因是,为了使 AI 和机器学习模型有效地学习,它们需要大量的数据。我们将在本章中进一步讨论对大量数据的需要。这是 AI 的限制因素。在医疗保健中,有许多法规阻止这些模型以任何方式利用数据,这是合理的。
-
来自许多地方的数据类型:正如我们接下来将要讨论的,来自许多不同地方有许多不同的数据类型。数据可以以文本、视频、图像或语音的形式存在,存储在许多不同的地方。除了能够从许多不同的位置访问数据这一主要挑战之外,这些机构还以各种格式存储数据。
正如之前提到的,FL 在医疗保健领域的应用是最早的工作之一。在这个广阔的领域内,FL 有几种方式可以帮助解决问题。让我们看看 FL 在以下一些领域有巨大潜力改变医疗保健系统的例子。这些领域包括医学影像、药物发现和电子健康记录(EHRs)。
让我们先来仔细看看 FL 在医学影像中的应用。
医学影像
在医学影像领域,正如在“基于深度学习和联邦学习的脑肿瘤诊断方法综合分析”中讨论的那样,人们对 FL 充满了乐观情绪,该分析列在本章的“进一步阅读”部分。这些高期望部分是由于需要解决的一些挑战以及 FL 克服这些障碍的能力。其中一项挑战是需要大量的数据。
随着医学影像行业继续发展更好的设备、程序和设施,每天都会产生大量的医学影像数据。这种数据的指数级增长为医疗保健提供者开发更好的机器学习模型和提高医疗保健质量提供了巨大的机会。
另一个对 FL 对医学影像产生积极影响的乐观原因已经证明的成功是机器学习(ML)——更具体地说,是深度学习(DL)。
让我们简要地看看 DL,以更好地理解为什么在处理大量数据时它如此重要。DL 是 AI 范畴下的 ML 的一个子空间。DL 与 ML 的不同之处在于它使用了几层被称为神经网络的层。关于 DL 和神经网络已有几本书被撰写,因此我们不会在本书中详细解释这些内容。对于 DL 和神经网络的更深入覆盖,Packt 的《Advanced Deep Learning with Python》是一本很好的读物。在我们的一般讨论中,我们将提供一个非常基本的解释。
下面的图,图 9**.1,展示了使用医学影像帮助分类脑肿瘤类型的神经网络的简单示例:

图 9.1 – 使用神经网络的肿瘤分类
在图例的左侧,我们有一个显示具有两种类型肿瘤之一的大脑图像。图像被分解成代表图像像素的数字。这些数字被添加到神经网络中。隐藏层为这些数字使用不同的权重,并通过激活函数产生不同的输出。最后,我们可以看到两个输出层。在这种情况下,有两种可能的输出。一个是肿瘤为 1 型,另一个是肿瘤为 2 型。
单个位置数据有限
如您所见,DL 模型需要大量的数据来训练。通常,单个数据存储库只有少量数据,这限制了任何模型很好地泛化的能力。
解决数据可访问性挑战的可能的解决方案
一种解决方案是利用隐私保护 FL,这样可以在保持敏感数据隐私的同时,利用多个中心可用的所有数据。
FL 使得可以在不共享敏感数据的情况下,在不同数据中心部署训练的大型 ML 模型。
在 FL 中,我们不是将数据移动到训练模型,而是将模型移动到数据,并且只带回从数据中收集到的智能,称为数据智能(IfD),将在本章的IfD 的潜力部分中进一步讨论。
示例用例 – 医院中的 ML
让我们通过一个例子来看看 FL 如何应用于医学影像数据。这个例子实际上是在一个专注于脑肿瘤的国际挑战赛中进行的。这里的目的是使用 MRI 扫描来分割肿瘤。
在这个例子中,我们将使用三家医院。我们将它们标记为医院 A、医院 B 和医院 C。每家医院都有本地存储的匿名私人医学影像数据。每家医院都从一个学习模型开始,您可以在下面的图中看到:

图 9.2 – 医院共享用于联邦学习的机器学习模型
每个医院都在本地运行模型;这创造了所谓的本地模型。重要的是要注意,在这个阶段,每个医院的本地模型都将不同。它们中的每一个都只在其医院的数据上进行了训练。
训练这三个本地模型的智能以模型参数的形式发送到一个集中服务器。服务器收集本地模型并将它们结合起来创建一个全局模型。这个全局模型,是三个本地模型智能的结合,随后被发送回每个医院,并仅在该医院的数据上再次进行本地训练。
再次强调,只有这些模型的智能被发送回服务器进行聚合。这个过程会重复进行,直到模型已经学到了它能学到的所有东西(称为收敛)。
利用联邦学习(FL),您可以在数据位于不同位置的情况下训练出表现如同所有数据都来自单一位置的模型。正如您所看到的,这种隐私保护方法的实施具有颠覆医学领域的力量。
现在我们来看看联邦学习(FL)如何改善药物发现领域。
药物发现
数据已成为我们现代世界的一种新型货币。对于制药公司来说,特别是,利用这些数据提供个性化医疗已成为一个主要焦点。在未来几年,能够利用更多数据的公司将更具竞争力。这将是任何组织未来成功的关键策略之一。
精准医疗
个性化医疗,也称为精准医疗,严重依赖于大量真实世界数据来实现这一点。此外,还需要机器学习算法来处理和分析这些数据,以便提取有意义的见解。正如我们将讨论的,目前获取大量真实数据非常困难,甚至可能是不可能的。
在图 9.3中,我们展示了精准医疗将立即产生影响的一些领域:

图 9.3 – 精准医疗影响许多领域
如您从图 9.3中看到的,精准医疗覆盖了广泛的不同领域和学科,如肿瘤学、健康、诊断、研究和健康监测。
目前,许多在医疗系统中部署的 AI 解决方案由于仅用代表患者群体一小部分的小数据集创建而失败。研究人员和开发者可以使用来自许多来源的数据来验证和改进模型,而无需拥有这些数据。
为了访问这些数据以便进行处理和分析,需要一种新的方法。这就是联邦学习(FL)发挥作用的地方。正如我们在整本书中讨论的,FL 提供所需的智能访问,而不是数据本身。
精准医疗是一种模型,它提出,而不是使用一刀切的方法,应该根据个人定制医疗保健,以在药物有效性和癌症治疗效果等方面取得更好的结果。为此,精准医疗严重依赖于大量真实世界数据。克服获取大量真实世界数据是实现大规模精准医疗的第一个障碍。
让我们看看当前的方法,看看 FL 如何提供所需的答案。
图 9.4 显示了当前常见的机器学习系统实现方法。在这里,数据被传输到中央服务器,所有数据都集中在一起,然后算法在汇总数据上训练。这被称为将数据带到模型的方法:
![图片 B18369_09_04.jpg]
![图片 B18369_09_04.jpg]
图 9.4 – 精准医疗现状
很容易想象以这种方式将许多医院的数据移动到集中地点的巨大成本。以这种方式处理数据也损害了数据安全,并使得合规性难以实现,如果不是不可能的话。
如图 9.5 所示,联邦学习(FL)的方法相当不同。它不是将数据带到模型中,而是将模型移动到数据中。这意味着机器学习模型在本地数据上训练,并且只将智能发送到 FL 服务器进行聚合,从而使各种本地模型能够从全局模型的更新中受益:
![图片 B18369_09_05.jpg]
![图片 B18369_09_05.jpg]
图 9.5 – 联邦学习中的精准医疗
联邦学习方法允许高效地转移模型和数据,同时符合法规要求。
IfD 的潜力
利用 FL 获取真实世界数据具有巨大的潜力,可以改善所有临床研究阶段。访问这类数据使我们能够利用收集到的智能和 IfD,可以显著加速药物发现的过程和步骤。在讨论 FL 的工作原理时,需要记住的一个重要想法是训练数据永远不会离开设备。
图 9.6 描述了通过训练过程从训练数据中提取机器学习模型的过程:
![图片 B18369_09_06.jpg]
![图片 B18369_09_06.jpg]
图 9.6 – IfD 图
如您在图 9.6 中所见,数据在本地用于训练机器学习模型。在 FL 中,模型位于设备本身上,在那里进行训练,并且只发送模型权重进行聚合——因此只有数据中的智能,而不是数据本身。
能够收集来自多个来源的 IfD(集成数据框架),以及包括视频、文本、语音和其他感官数据在内的广泛数据类型,可以帮助提高注册流程,无论是速度还是找到合适的匹配以进行药物和治疗的研究和开发。
这对于罕见疾病和癌症尤为重要。当以这种方式进行识别时,受试者可以被告知试验机会。
联邦访问数据和收集 IfD 可以打开全球大量数据的访问。这允许聚合数据存储库挖掘足够多的符合协议标准的患者。潜在地,这可以使试验的所有参与者都能接受实际药物而不是安慰剂。
最终,没有稳健的 AI,就无法实现大规模的精准医疗,而稳健的 AI 只能通过大量真实世界数据进行训练。使用 FL 可以允许改进结果测量。在未来几年中,联邦方法有可能以新的方式推动新医疗治疗方法发现的进步和创新,这些方式以前是不可能的。
在以下图中,图 9.7,我们展示了 FL 如何收集个体化数据(IfD)的通用视图:

图 9.7 – FL 收集 IfD 的通用视图
正如这里图 9.7所示,所有数据都保持在每个组织内部隔离,并且不会传输到联邦服务器。
让我们继续前进,讨论一个与电子健康记录(EHRs)相关的联邦学习(FL)应用。
EHRs
电子健康记录(EHR)是一组系统性和数字化存储的健康信息。这些记录旨在在适当的时候与医疗保健提供者(HCPs)共享。根据HealthIT.gov的统计数据,截至 2017 年,美国 86%的门诊医生已经采用了 EHRs。
图 9.8展示了从各种来源收集的 EHRs,例如医院和保险公司。在此图中,我们使用术语电子病历(EMRs)和个人健康记录(PHRs):

图 9.8 – EHRs
这种采用 EHRs 为医疗保健组织之间的有益合作奠定了基础。正如我们在整本书中讨论的那样,能够访问更多真实世界数据使得基于这些数据训练的 AI 模型更加稳健和有效。
虽然已经建立了这个基础,但在多个机构之间共享 EHR 数据方面,传统的机器学习(ML)方法仍然面临许多挑战。这些问题包括隐私问题和法规以及数据标准化。其中一个主要问题是将数据存储在中央数据存储库(CDRs)中,如图 9.9 所示,其中存储了各种形式的地方数据以训练创建 ML 模型。

图 9.9 – 集中式数据挖掘方法
由于数据隔离问题,这种 CDR 方法并不理想,这一问题将在下一节中讨论。
数据隔离问题
使用 CDR(临床数据记录)进行数据存储带来了许多问题。例如,包括数据泄露、严格的法规以及设置和维护的高成本。
数据存储方法对数据本身的质量和可用性的影响同样重要。在单一中心数据上训练的 ML 模型与从多个地点收集的数据相比,通常无法很好地泛化。
FL 允许跨多个组织进行模型训练协作,从而在不违反数据隐私法规的情况下产生卓越的模型性能。
让我们看看一个 FL 应用在 EHRs 中的例子。
电子健康记录中的表示学习
研究人员已经将联邦学习(FL)应用于电子健康记录(EHRs)中的表示学习,如“进一步阅读”列表中提到的两阶段联邦表型与患者表示学习。他们使用 FL 结合自然语言处理(NLP)对患者数据进行表型和表示学习。在第一阶段,基于几家医院的某些医疗记录创建患者数据的表示,而无需共享原始数据本身。所学习到的表示并不局限于任何特定的医疗任务。在第二阶段,使用从学习到的表示中提取的相关特征,以联邦方式训练特定表型工作的 ML 模型。
FL 已被展示为在医疗保健领域 AI 发展的进步中,作为当前方法的有效替代,无论是在药物发现、医学影像还是电子健康记录(EHRs)的分析中。
让我们继续探讨金融行业,这是联邦学习(FL)的另一个有前景的应用案例。
将 FL 应用于金融行业
仅在美国,金融服务公司每年在合规性上就花费数十亿美元以对抗洗钱,然而,当前系统如此无效,以至于不到 1%的洗钱活动被阻止。事实上,据估计,公司花费的金额大约是他们从这种犯罪活动中能够追回金额的 100 倍。只有一小部分交易被反洗钱(AML)系统捕获,而其中更小比例的警报最终按照 1970 年银行保密法(BSA)的要求报告在可疑活动报告(SARs)中。
保守估计,来自银行网络的情报价值远远高于任何一家银行的情报。这是因为你可以看到钱从哪里来,也看到钱流向了哪里。
反洗钱(AML)
当前的 AML 系统需要重大改进,需要克服几个挑战。许多隐私法规被实施以保护个人财务数据。这些法规从机构到机构、从地区到地区各不相同。
一种解决方案是金融机构之间的合作。这种合作将允许这些机构以互利的方式共享从自身数据中收集到的情报。而不是在合作者之间移动数据,机器学习(ML)模型本身将在每个机构本地部署。这将仅允许共享 IfD(集成数据),并使能够利用收集到的情报的每个合作者受益。正如我们讨论的那样,联邦学习(FL)具有这种能力。
在 FL 中,模型本身从一家机构移动到另一家机构。在这个过程中,模型通过从更多数据中学习调整参数,变得更加智能。这与 FL 的这两种替代方案不同:
-
一种选择是将来自合作金融机构的数据收集到一个中央存储库中。然而,由于客户隐私法规,这种方法不可行。
-
另一种方法是根据需要共享某种标识符。然而,由于隐私法律和法规的限制,这并不可能,只能作为调查过程的一部分使用。
要能够使用 FL,我们需要确保数据的隐私和安全保持完整。为此,我们必须确保模型在没有隐私违规的情况下在金融机构中接受训练。最后,我们需要确保所有与模型参数更新相关的通信,发送到联邦服务器的是安全可靠的。
FL 可以提高全球反洗钱(BSA)和反洗钱(AML)制度的有效性、效率和公平性。在下一节中,我们将探讨在反洗钱各学科中实施交易监控时使用 FL。
对现有 AML 方法的解决方案。
在反洗钱各学科中开发 FL 方法包括客户入职这一基本主题,并可能有助于使用非传统信息来验证潜在客户的身份。
需要一种新的方法,可以利用复杂的技术来提高风险检测系统的意识和效率。
这种方法应该能够实现以下功能:
-
使企业和监管机构能够在不共享敏感或受保护数据的情况下相互学习。
-
增强企业准确识别真实风险并减少无根据的风险报告的能力。
-
提高企业在决定是否服务于特定市场时的风险-收益计算。
反洗钱框架在防止任何通过恐怖主义、洗钱、欺诈或人口贩卖等非法目的使用全球金融系统造成伤害的国家利益中发挥着至关重要的作用。
尽管在洗钱风险检测系统上投入了巨大的资金和关注,但系统已经破裂。企业投入大量资源以满足反洗钱(AML)要求,但很少得到关于其风险报告质量的反馈。
两个关键因素驱使企业退出特定市场运营:
-
第一个是钱服务行业内部风险评级活动的成本
-
第二个关键因素是与非法金融活动相关的金融机构的监管风险和声誉影响
因此,全球范围内,代理银行关系下降了 25%。自 2009 年以来,新兴市场中有 25% 的银行报告了代理银行损失。
在 图 9**.10 中,交易报告被描绘出来,每个机构的危险模式数据库反映了其非法活动的经验:

图 9.10 – 机构报告的疑似非法活动
机构不一定知道其竞争对手正在捕捉到的模式或政府知道哪些标记的交易是可疑的还是真实的。公司很少得到关于他们提交的报告准确性的及时反馈。结果是,公司缺乏改善其风险检测能力最关键的信息:关于确认问题的及时信息。
那么,想象一下在这种场景下更有效、更高效、更公平的交易监控。银行充当类似公用事业的中心。结合强大的计算机和智能算法可以对不同机构的进行数据评估。学习到风险模式的机器学习模型随后会在参与公司之间移动,以捕捉模式并从每个机构的风险中学习。所有这些都可以在不共享敏感或受保护数据的情况下完成。这如图 图 9**.11 所示:

图 9.11 – 银行生态系统中的数据和情报流动
在 FL 方法中,银行创建了一个分类算法,该算法在每个参与公司的数据上训练。银行开发了一个关键模型和模型参数,这些参数反映了所有参与公司以及政府拥有的数据的见解。银行将关键模型和模型参数分发给参与公司,同时数据留在每个机构。这些分布式模型通过从本地数据中学习并随后将它们发送回银行来采用这些公司的风险模式。
FL 在 AML 领域的演示
TieSet, Inc. 的研究人员在 STADLE 上对 AML 领域应用 FL 进行了实验,使用 PaySim 移动货币模拟器生成的某些合成交易数据 (www.kaggle.com/ealaxi/paysim1)。他们使用了带有逻辑回归的监督学习,其中模型特征包括时间、金额以及原始账户和目标账户的新旧余额。数据集有 636,2620 笔交易(8,213 笔欺诈交易和 635,4407 笔有效交易),这些交易被分成 10 个单独的本地代理。
图 9**.12 是将联邦学习(FL)应用于反洗钱(AML)的结果,图中展示了每轮训练中的精确度得分和 F1 得分。在图中,粗线代表聚合模型的性能,细线代表仅使用本地数据进行单独训练的个体代理的结果:
![图 9.12 – 将 FL 应用于 AML 的结果(粗线:聚合模型,细线:个体代理单独训练)]
![img/B18369_09_12.jpg]
图 9.12 – 将 FL 应用于 AML 的结果(粗线:聚合模型,细线:个体代理单独训练)
正如图 9**.12所示,聚合模型的表现相当稳定,在精确度和 F1 得分方面始终保持在 90%以上。FL 可以将欺诈交易从总欺诈交易额 1,241,770,000 美元减少到 65,780,000 美元,这意味着只有 5.3%的欺诈交易丢失。
让我们通过查看 FL 为风险检测提供的益处列表来结束本节。
FL 对风险检测系统的益处
将 FL 应用于风险检测系统在金融领域有以下几个好处:
-
FL 可以创建一个更大的风险检测算法数据集进行训练
-
提高非法活动检测的准确性
-
为组织提供了一种协作方式
-
企业可以进入新市场
在您的金融机构内识别洗钱策略的能力受限于您所能访问的数据。数据共享限制使得合作变得困难,甚至不可能。FL 为金融行业带来的解决方案和优势众多。额外的优势包括更好的运营效率和人力资本更好的分配。由于能够从客户数据中提取智能,FL 的应用在金融空间中几乎无限制。
在下一节中,我们将讨论 FL 在几个新兴技术中的应用时,我们将稍微转换一下话题。
FL 与边缘计算相结合
本章的这一部分是不同领域的混合,其中一些是新兴技术。这些领域都非常相互关联,正如我们将要讨论的。许多这些技术相互依赖以克服它们自己的挑战和限制。将这些技术与 FL 结合是一种特别有力的技术组合,这将是未来几年进步和创新的关键。
在接下来的十年里,我们将看到边缘计算能力、物联网(IoT)和 5G 连接性的扩展带来的几个变化。我们将看到数据量和传输速度的指数级增长。我们将继续看到更多隐私法规的实施,以保护私人用户数据,以及自动化和分析领域的爆炸式增长。
在 5G 之上的物联网边缘计算
要实现智能设备的全部潜力,这些设备必须能够连接到一个大大改进的网络,比如 5G。事实上,到 2023 年底,预计全球将有 13 亿 5G 服务用户。与边缘计算一样,5G 网络对于物联网连接至关重要。结合这些技术将有助于为智能设备铺平道路。
图 9**.13 展示了各种事物,在物联网框架内,边缘计算能力连接到云和数据中心:

图 9.13 – 边缘计算与互联网
然而,许多这些物联网设备缺乏足够的安全功能。除了像通用数据保护条例(GDPR)这样的法律和法规之外,我们还可以期待实施额外的政策来保护用户数据。本质上,随着时间的推移,对提取 IfD 解决方案的需求将继续增加。
让我们看看 FL 应用于边缘计算的一个示例。
边缘 FL 示例——目标检测
边缘计算是一种使用分布式计算将计算和数据存储尽可能靠近数据源的架构。理想情况下,这应该会降低延迟并节省带宽。以下是如何利用 FL 与不同类型的边缘设备相结合的示例。
技术设置
在这个示例中,使用了三个设备来演示使用边缘设备进行目标检测的 FL。一个是运行在 Nvidia 的 Jetson 上的 EIS200 边缘微服务器,操作系统为 Ubuntu。第二个设备是树莓派,使用 Raspberry Pi OS,第三个设备是一个简单的常规 PC,操作系统也是 Ubuntu。这些机器分别使用独特的数据集训练了一个目标检测模型。
如何做
EIS200 在鱼、肉和番茄的图片上进行了训练,标签为鱼、肉和蔬菜。树莓派在鱼、肉和茄子的图片上进行了训练。显然,在这里,番茄被茄子所取代。然而,标签保持不变——鱼、肉和蔬菜。同样,常规 PC 在鱼、肉和葱的图片上进行了训练,标签仍然是鱼、肉和蔬菜。
如您所预期的那样,每个环境都含有不同蔬菜的偏见数据——比如番茄、茄子、和葱——但所有这些蔬菜都有一个相同的标签,即蔬菜。
它是如何工作的
首先,模型是用 EIS200 的番茄图片进行训练的。正如您所预期的那样,只有番茄被正确地标记为蔬菜,而茄子和葱被错误地标记。
同样,树莓派仅用茄子图片训练的模型正确地识别了茄子。其中两根葱中的一根被标记为蔬菜,但另一根被识别为鱼。正如预期的那样,常规 PC 的模型只识别葱为蔬菜。
三个代理中没有一个能够正确标记所有三种蔬菜,正如我们所预期的。接下来,它们连接到一个名为 STADLE 的 FL 平台,由 TieSet 公司开发:


图 9.14 – 使用 FL 检测番茄、茄子和大葱的演示,其中每个机器上的数据集分布不同
STADLE 聚合器在 AWS 中以实例运行。在这里,每个环境都有一个独特的、只包含一种蔬菜的数据集。与 STADLE 平台连接后,每个代理使用本地数据进行训练。经过几个训练周期后,模型权重从代理发送到聚合器。然后,这些权重被聚合并发送回代理以继续训练。这种聚合周期的重复产生了无偏重的量。
检查结果
FL 模型能够正确检测和标记所有三种蔬菜,如图9**.15所示。这是 FL 在消除偏差方面的一个简单示例:


图 9.15 – 使用三个边缘设备进行演示的结果
如前所述,所有模型训练都是在边缘设备的本地存储中进行的。模型在本地数据上训练,然后只将参数权重发送到联邦服务器进行聚合。联邦服务器平均化模型。如果您还记得前面的章节,这被称为 FedAvg。联邦服务器然后将改进和更新的模型发送回边缘设备。因此,再次强调,只收集 IfD,而不是数据本身。
现在,让我们在下一节中看看汽车领域的另一个边缘示例。
使用 FL 实现自动驾驶
在 AI 行业中,特别是汽车领域,边缘计算与 ML 获得了显著的兴趣。例如,自动驾驶需要低延迟和实时响应才能正确运行。因此,FL 成为分布式数据处理和训练方面汽车领域的最佳解决方案之一。
将计算和存储卸载到边缘物联网设备使得管理自动驾驶应用的云系统变得更小、更便宜。这是从中心云基于 ML 范式转向 FL 范式最强大的好处。
现代汽车已经配备了具有复杂计算能力的边缘设备。高级驾驶辅助系统(ADASs)是自动驾驶汽车的基本功能,计算在车上进行。它们也需要大量的计算能力。
即使预测发生在自动驾驶车辆中,模型也是使用在本地服务器或云中的常规、昂贵的训练系统进行训练和准备的。
如果数据量变得更大,训练过程将变得更加计算密集和缓慢,并且还需要大量的存储空间。
为了避免这些问题,需要使用联邦学习,因为更新的机器学习模型在车辆和服务器之间传递,服务器存储着车辆的驾驶模式和来自车载摄像头的实时图像流。联邦学习再次可以根据用户同意并遵守隐私和区域法规来工作。
图 9**.16 介绍了用于提高 ADAS 安全驾驶性能的分布式联邦学习,这是 TieSet, Inc. 与其技术合作伙伴进行的真实用例:

图 9.16 – 使用多个聚合器提高 ADAS 安全驾驶性能的分布式联邦学习
TieSet, Inc. 的 STADLE 系统集成了 ADAS,旨在提供舒适性和个人安全措施,尤其是针对老年人,为汽车产品提供了优化的转向控制辅助。通过先进的计算机视觉和强化学习(RL)技术,他们实现了一种设计,能够提供及时的危险情况意识,并智能地学习最佳的个性化驾驶策略。
虽然个性化是设计的主要焦点,但个人数据的使用带来了实质性的隐私问题。由 STADLE 平台提供的 FL 框架为克服这一障碍提供了现实解决方案。该架构以协作形式呈现,通过人工智能智能交换在边缘用户之间进行分布式机器学习训练,避免了数据传输并确保了数据隐私。此外,聚合模型可以应对超出驾驶员个人经验的多种风险和不可预测的情况。
在使用真实汽车进行概念验证的过程中,他们成功展示了所设计的 RL 模型能够有效地生成针对使用 STADLE 聚合框架的驾驶员的定制化转向策略。
在下一节中,我们将讨论联邦学习如何应用于机器人领域。
将联邦学习应用于机器人技术
在机器人系统和应用中,机器学习已经成为完成必要任务的一个不可或缺和基本的部分。计算机视觉已经发展到使机器人系统在许多任务上表现出色,例如图像分割、目标检测和分类,以及自然语言处理和信号处理任务。机器学习可以处理许多机器人任务,包括感知、路径规划、传感器融合以及在制造环境中抓取检测到的物体。
然而,机器人在机器学习领域也面临着许多挑战。首先是训练时间。即使数据量足够训练机器学习模型来解决上述问题,训练一个真实的机器人系统也需要数周或数月。同样,如果数据不足,它可能会显著限制机器学习模型的表现。通常,数据隐私和可访问性成为收集足够数据来训练机器人机器学习模型的问题。
正因如此,联邦学习框架被认为是机器人领域的一个关键解决方案。TieSet, Inc. 的研究人员开发了一个系统和方法,允许机器人操作器和工具与其他机器人共享其操作技能(包括伸手、抓取放置、握持和抓取)以及各种类型和形状的物体,并使用其他机器人的技能来改进和扩展自己的技能。
该系统涵盖了创建机器人通用操作模型的方法,该模型通过从各种机器人代理中众包技能并保持数据隐私来持续改进。他们提出了一种新的架构,其中多个由人工智能驱动的机器人代理通过提交其模型到一个聚合器来协同训练全局操作模型。这种通信使得每个代理能够通过接收一个最优更新的全局模型来利用其他代理的训练结果。
图 9.17 展示了机器人联邦众包全局操作框架的工作架构:

图 9.17 – 机器人联邦众包全局操作框架的架构
基于前图的架构,在模拟设置中,他们为抓取盒子、球、鸭子和泰迪熊等个别任务准备了五个机器人臂。使用 TieSet, Inc. 的 STADLE 平台,该平台可以进行异步联邦学习,这些臂的机器学习模型被持续聚合。最终,联邦机器人机器学习模型可以以更高的性能(80% 的成功率)抓取所有这些物体,如图 图 9.18 所示:

图 9.18 – 机械臂可以通过不同任务进行交叉训练以提高精度和效率
基于 STADLE 的联邦学习可以显著减少训练机器人和机器学习用于生产线的时间,使用计算机视觉。联邦性能远优于单独训练机器人,学习时间也比单独训练机器人快得多。
在下一节中,我们将讨论大规模人工智能,即使有大量设备和连接的环境,学习也应该持续进行,物联网应演变为智能互联网。
向智能互联网迈进
在本节中,我们将讨论为什么在物联网和 5G 等可扩展技术的最新发展中,FL 非常重要。与上一节一样,AI 需要在大规模学习中保持学习的领域包括自动驾驶、零售系统、能源管理、机器人和制造,所有这些在边缘侧产生大量数据,其中大部分数据需要完全学习以生成性能良好的机器学习模型。
随着这一趋势的发展,让我们来看看智能互联网的世界,在这个世界里,学习可以在边缘侧发生,以应对动态环境和连接到互联网的众多设备。
介绍 IoFT
物联网涉及智能和连接的系统。它们之所以智能,是因为信息被共享,智能被提取并用于某些目的——例如,设备的预测或控制。它们通常连接到云端,并能够从许多端点收集数据。
图 9.19显示了随着时间的推移,数据越来越多:

图 9.19 – 当前物联网系统
如图 9.19所示,在当前的物联网流程中,必须上传和存储大量数据在云端。
模型针对特定目的进行训练,例如预测性维护和文本预测。最后,训练好的模型被发送回边缘设备。
如您所见,当前方法存在几个问题:
-
需要大量的存储空间
-
由于数据量,延迟受到影响
-
由于数据移动导致的隐私问题
为了克服这些问题,FL 扮演着重要的角色。
联邦物联网(IoFT)是一个最初由密歇根大学的研究者提出的想法,其论文《联邦物联网》(IoFT)列在本章的进一步阅读部分。IoFT 是一个扩展框架,将物联网与 FL 的概念相结合。随着边缘侧的计算能力显著提高,AI 芯片正迅速进入市场。即使是智能手机,现在的计算能力也非常强大,小型但功能强大的计算机通常连接到大多数边缘设备。
因此,由于边缘设备的计算能力提高,机器学习模型训练过程被降低到边缘,物联网将数据发送到服务器的功能可以用来将机器学习模型传输到云端。这也是一种非常有效的方法,可以保护边缘设备上的隐私数据,例如手机。
让我们看看图 9.20中展示的 IoFT 的一个例子。

图 9.20 – IoFT 的一个例子
IoFT 的潜在应用包括分布式制造、交通交叉口控制和能源控制等。
理解 FL 在 Web 3.0 中的作用
FL 可以集成到 Web 3.0 技术中,以加速智能互联网的采用。由机器学习模型表示的智能可能是特定个人或行业的财产。同时,如果它是可以促进全球人们该机器学习模型学习过程的公共资产,那么它也可以被认为是公共资产。无论是私人知识产权还是公共资产,通过利用 Web 3.0 技术,智能可以以去中心化的方式管理和进化。因此,越来越多的人将获得由人们共同训练的智能带来的好处,这将导致我们整个社会在各个领域和各种应用中的真正创新。
将 FL 应用于大数据的分布式学习
在本节中,我们将讨论如何在大数据的背景下将联邦学习(FL)应用于分布式学习。
对于大数据,FL 可能不太涉及与隐私相关的问题,因为用于智能目的的数据已经拥有。因此,它可能更适合大数据的高效学习和显著提高训练时间,以及降低使用大型服务器、计算和存储的成本。
在大数据上进行分布式学习有几种方法,例如构建适用于不同类型服务器的特定端到端机器学习堆栈,如参数服务器,或者在 Hadoop 和 Spark 等大数据平台之上利用某些机器学习方案。还有一些其他平台,如 GraphLab 和 Pregel。您可以使用任何库和方法,例如具有低级机器学习实用工具的随机近端下降法和坐标下降法。
这些框架可以在计算上支持机器学习模型的并行训练,但无法将数据源分配到不同的机器上以分布式方式本地训练,尤其是在训练环境分散在互联网上时。通过 FL,您只需通过同步模型联邦即可简单地聚合不同分布式机器的学习结果,但您确实需要开发一个精心设计的平台来协调分布式学习的持续运行,包括适当的模型存储库和版本控制方法。
大数据上执行分布式学习的示例在图 9.21中描述。

图 9.21 – 大数据中分布式学习的集成
在*图 9.21 的例子中,数据源,通常是很大的,被分割成多个数据源,以便分散到不同的机器或实例中进行训练。在联邦学习(FL)框架内,来自分布式环境的训练模型都被汇总。训练和汇总后的模型随后进入机器学习操作(ML Ops)的过程,以进行性能验证和持续监控,以及模型操作(Model Ops**)。
在前一个场景之上,可以进一步的做法是结合其他数据源的洞察。在这种情况下,联邦学习(FL)可以优雅地结合其他数据源的洞察,并很好地协调分布式环境中直接创建的其他形式智能的集成。这样,你还可以创建集中式机器学习(ML)和分布式机器学习(ML)的混合模型。
摘要
在本章中,我们讨论了不同行业在人工智能进步方面面临的各种挑战。大多数挑战都与数据可访问性有关。数据隐私法规、缺乏真实数据和数据传输成本都是我们期望联邦学习(FL)继续帮助解决的独特且具有挑战性的问题。
在本章中,你了解了联邦学习(FL)在医疗保健、金融、边缘和物联网(IoT)领域等越来越多地发挥重要作用的用例。联邦学习(FL)提供的隐私遵守对于医疗保健和金融行业尤为重要,同时联邦学习(FL)可以为许多边缘人工智能和物联网场景在可扩展性和学习效率方面增加显著价值。你还学习了如何将联邦学习(FL)应用于大数据的分布式学习,以减少训练时间和成本。
在下一章和最后一章中,我们将通过讨论联邦学习(FL)在未来十年预期将发挥关键作用的非常激动人心的未来趋势和发展来结束本书。
参考文献
本章节中使用了以下参考文献:
-
金融服务公司花费 1809 亿美元用于金融犯罪合规:
www.prnewswire.com/news-releases/financial-services-firms-spend-180-9-billion-on-financial-crime-compliance-according-to-lexisnexis-risk-solutions-global-study-301036194.html
进一步阅读
要了解更多关于本章所涉及的主题,请参阅以下参考文献:
-
*Naeem A., Anees T., Naqvi RA., Loh WK. 对基于深度和联邦学习的脑肿瘤诊断方法的综合分析. J Pers Med. 2022 年 2 月 13 日;12(2):275. doi: 10.3390/jpm12020275 PMID: 35207763; PMCID: PMC8880689.
-
Ng, Dianwen, 等人. 联邦学习:一个协作努力,以实现为具有小型标记数据集的各个站点提供更好的医学成像模型。定量影像医学与外科 11.2 (2021):852*:
www.ncbi.nlm.nih.gov/pmc/articles/PMC7779924/ -
Rieke, Nicola, 等人. 联邦学习在数字健康领域的未来。NPJ 数字医学 3.1 (2020):1-7*:
www.nature.com/articles/s41746-020-00323-1.pdf -
*Dang, Trung Kien, 等人. 联邦学习在电子健康记录中的应用. ACM 智能系统与技术交易(TIST)(2022):
dl.acm.org/doi/pdf/10.1145/3514500 -
Liu, Dianbo, Dmitriy Dligach, 和 Timothy Miller. 两阶段联邦表型学习和患者表示学习。会议论文。计算语言学协会。会议。2019 年卷。NIH 公共访问,2019*:
arxiv.org/pdf/1908.05596 -
*Kontar, Raed, 等人. 联邦事物互联网(IoFT). IEEE Access 9 (2021): 156071-156113:
ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=9611259 -
深度联邦学习在自动驾驶中的应用:
arxiv.org/pdf/2110.05754.pdf -
联邦学习在大数据中的应用:关于机会、应用和未来方向的调查:
arxiv.org/abs/2110.04160
第十章:未来趋势和发展
智慧将推动下一代技术,而不是大数据。正如在第一章“大数据和传统人工智能的挑战”中讨论的那样,大数据系统存在一些问题,世界正逐渐从数据为中心的时代过渡到智能为中心的时代。联邦学习(FL)将在智慧驱动技术中扮演核心角色。因此,现在是欢迎集体智慧世界的时刻。
在本章中,我们将讨论由联邦学习(FL)带来的范式转变所驱动的未来人工智能技术的方向。对于许多人工智能领域,如对隐私敏感的领域和需要可扩展性的机器学习(ML)领域,FL 的好处和潜力已经非常显著,这主要归功于 FL 设计上自然支持的隐私保护和分布式学习方面。你将了解 FL 的不同类型以及该领域的最新发展努力,如分片学习和群集学习技术,这些可以被视为增强 FL 的进化框架。
此外,联邦学习(FL)创造了一个新的概念——智能互联网,在这个互联网中,人和计算机交换的是智慧,而不仅仅是数据本身。区块链技术进一步加速了面向每个人的智能互联网。这种智能互联网可以形成一个新的定义——集体智慧,它推动着从数据为中心的方法向智能为中心或模型为中心的方法的又一创新。
最后,我们将分享一个集体愿景,在这个愿景中,联邦学习(FL)在协同创造由世界各地许多人和机器学习到的智慧中扮演关键角色。
在本章中,我们将涵盖以下主题:
-
观察未来人工智能趋势
-
联邦学习的研究和发展
-
探索集体智慧
观察未来人工智能趋势
如下节所述,大多数行业领导者现在都意识到集中式机器学习的局限性。
集中式机器学习的局限性
当展望人工智能的未来时,首先要知道的一个事实是,许多公司今天都在努力从他们拥有的数据中提取智能和获得洞察。通常,组织和企业收集的数据中超过一半都没有被使用。传统的机器学习和数据科学方法需要在分析和管理机器学习模型之前,将数据组织并整合到数据湖和存储中。你需要复制并移动数据,这会导致从数据中提取的智能价值的实现和交付延迟,同时伴随着一定的运营风险和复杂性。
此外,大多数企业公司生成的大量数据将在传统的集中式数据中心或云之外创建和处理。以集中方式处理数据以生成洞察力变得越来越不切实际且效率低下。
此外,根据一些市场报告,大多数最大的全球组织和公司至少会探索一次联邦学习,以创建更加准确、安全和环保的模型。
话虽如此,许多行业和市场逐渐意识到分布式和联邦学习范式的重要性,因为他们正面临着当前集中式大数据 AI 训练不可避免的问题和限制,如第一章中所述,大数据和传统 AI 的挑战。联邦学习将模型带到训练过程所在的数据处,而不是将数据带到模型处。因此,联邦学习被认为是数据科学和机器学习的未来。
在下一节中,让我们总结一下为什么联邦学习(FL)对那些公司有益,尤其是那些面临上述问题的企业。
重温联邦学习的益处
在本节中,我们将总结本书中介绍过的联邦学习的益处。
模型准确性和泛化能力的提升
联邦学习通过在本地对分散的数据集进行训练,将学习持续地纳入全局模型,实现了协作和分布式学习,从而可以提高机器学习模型的性能。这样,可以产生更准确和泛化的机器学习模型。
进一步的隐私和安全
联邦学习提供了隐私和安全优势,因为它不会因为其设计和安全机制而要求私有和原始数据,正如我们在第二章中讨论的什么是联邦学习和第九章中讨论的联邦学习应用的关键用例案例研究。因此,联邦学习降低了数据滥用、泄露或敏感信息暴露的风险。联邦学习也符合许多隐私法规,如通用数据保护条例(GDPR)、加州消费者隐私法案(CCPA)和健康保险可携带性和问责法案(HIPAA)。
提高速度和效率
联邦学习还以其高计算效率而闻名,这可以加速机器学习模型的部署和测试,以及减少通信和计算延迟。由于联邦学习的去中心化特性,模型交付和更新的延迟最小化,从而使得全局模型能够近乎实时地进行预测。对于时间敏感的机器学习应用来说,智能的实时交付和更新是非常有价值的。
FL 还有助于通过克服系统异质性和不平衡的数据分布来减少带宽和能源消耗,这导致最小化数据存储和传输成本,这些成本也可以显著减少环境影响。
向分布式学习迈进,以实现更高的隐私性和训练效率
目前,AI 是在大型计算服务器上训练的,通常在大数据公司的大型机器上发生。
正如超级计算机时代所看到的,它可以在一台机器或一台机器集群中处理大量数据和任务,技术的进化过程始于一个中心位置,并逐渐过渡到分布式环境。
在 AI 领域,同样的事情即将发生。现在,数据湖概念在组织训练 ML 模型方面非常流行,但 ML 已经需要分布式学习框架。
FL 是一种将训练过程分布到多个节点上的绝佳方式。正如许多研究报告所示,大多数数据并未被充分利用来提取 ML 模型的洞察力。
有些公司和项目正在尝试使用 FL 作为一种强大的分布式学习技术,例如 Devron 提供的平台(devron.ai)、FedML(fedml.ai)和 STADLE(stadle.ai)。这些平台已经解决了集中式 AI 的局限性部分中讨论的问题,并在各种用例中显著提高了 ML 过程,正如重访 FL 的好处部分所述。
根据我们讨论的 AI 趋势,让我们在下一节中看看与 FL 相关的正在进行的研究和发展,这些研究是由前沿公司现在进行的。
FL 的持续研究和开发
我们现在来讨论正在全球学术界和工业界进行的研究和开发项目。让我们从 FL(联邦学习)的不同类型和方法开始,然后继续探讨进一步增强 FL 框架的持续努力。
探索各种 FL 类型和方法
在这本书中,我们探讨了 FL 系统的最基本算法和设计概念。在现实世界中,我们需要深入了解哪些 FL 框架可用,以从这些算法中提取最佳性能。根据数据场景和用例,FL 中有几种方法,如下所示:
-
水平 FL 和垂直 FL
-
集中式 FL 和去中心化 FL
-
跨部门 FL 和跨设备 FL
现在,让我们在以下各节中看看每种 FL 类型。
水平 FL 和垂直 FL
水平联邦学习(Horizontal FL)使用所有分布式设备上具有相同特征空间或模式的数据库集(www.arxiv-vanity.com/papers/1902.04885/)。这实际上意味着数据集共享相同的列,但行不同。大多数现有的联邦学习项目都是基于水平联邦学习。具有水平联邦学习的数据库和训练过程很简单,因为数据集是相同的,只是数据分布和要学习的数据输入不同。水平联邦学习也称为同质或基于样本的联邦学习。
垂直联邦学习(Vertical FL)适用于不同数据集共享相同的样本 ID 空间但特征空间不同的情况。您可以查看这篇论文(https://arxiv.org/pdf/2202.04309)以获取有关垂直联邦学习的更多信息。通过联邦学习将这些不同的数据库联系起来可能具有挑战性,特别是如果数据的不同唯一 ID 不同。垂直联邦学习的关键思想是通过使用具有不同属性集的分布式数据集来改进机器学习模型。因此,垂直联邦学习可以垂直处理具有相同样本空间中不同属性的分区数据。垂直联邦学习也称为异质或基于特征的联邦学习。
集中式联邦学习和去中心化联邦学习
集中式联邦学习(Centralized FL)是目前最常见的方法,大多数平台都采用这种框架。它使用集中式服务器来收集和聚合不同的机器学习模型,并在所有本地数据源上进行分布式训练。在这本书中,我们专注于集中式联邦学习方法,其中本地训练代理将学习结果传达给集中式联邦学习服务器以创建全局模型。
去中心化联邦学习(Decentralized FL),另一方面,不使用集中式服务器来聚合机器学习模型。它要求在本地数据源上训练的各个机器学习模型之间相互通信,而不需要一个主节点。在这种情况下,模型权重从每个单独的数据集传输到其他数据集以进行进一步训练。如果不受信任的方能够访问智能,则可能容易受到模型中毒的影响,这是从对等框架中衍生出的一个常见问题。
跨领域联邦学习和跨设备联邦学习
跨领域联邦学习(Cross-silo FL)是指机器学习模型在跨越任何功能、组织和监管障碍的数据上训练的情况。在这种情况下,大数据通常存储在更大的存储空间中,并具备如云虚拟机这样的训练计算能力。在跨领域联邦学习的情况下,领域/训练环境的数量相对较小,因此在联邦学习过程中不需要太多代理。
跨设备 FL是模型需要在大规模训练的情况,通常在边缘设备上进行,例如手机、物联网(IoT)设备、Raspberry Pi 类型的设备等。在这种情况下,大量设备连接起来以聚合机器学习模型。在跨设备 FL 的情况下,限制基本上在于这些边缘设备的低计算能力。该框架还需要处理大量断开和无效的设备,以进行一致和持续的 FL 过程。训练过程及其数据量也应受到限制。
这就总结了可以应用于机器学习应用中各种场景的不同类型的 FL。有一些新技术试图增强 FL 框架,使其演变成下一代具有 FL 的人工智能技术。让我们在下一节中探讨几个高级方法。
理解基于 FL 的增强分布式学习框架
正在进行着进一步增强 FL 或分布式学习框架的努力。
分割学习
分割学习是在麻省理工学院媒体实验室开发的,是一种新兴的分布式学习技术,它使机器学习模型能够分割成多个部分,在分布式客户端上训练这些分割的机器学习模型,并在最后进行聚合。分割学习也不必共享数据,因此也被认为是保护隐私的人工智能。
整体框架类似于 FL。然而,不同之处在于神经网络被划分为多个部分,这些部分将在分布式客户端上进行训练。然后,该神经网络部分的训练权重将被传输到服务器和客户端。这些多个部分的权重将在下一次训练会话中持续训练。因此,分布式客户端之间不共享任何原始和私有数据,只有每个部分的权重被发送到下一个客户端。
尤其是 SplitFed(arxiv.org/abs/2004.12088)是另一种结合分割学习和 FL 的高级技术。SplitFed 在 FL 客户端和服务器之间分割深度神经网络架构,以实现比 FL 更高的隐私级别。它比基于 FL 并行学习范式的分割学习提供了更好的效率。
群体学习
群体学习是基于区块链技术的去中心化机器学习解决方案,特别设计用于使企业行业能够利用分布式数据的力量,从而保护数据隐私和安全。
这可以通过各个节点共享从本地数据推导出的机器学习模型的参数来实现。
从分布式客户端共享的参数合并到一个全局模型中。与正常 FL 的不同之处在于,合并过程不是由中央服务器执行的。分布式节点和客户端选择一个临时领导者来执行合并。这就是为什么蜂群学习真正实现了去中心化,同时也提供了更大的容错性和弹性。分布式代理具有网络的集体智能,而不需要将本地数据共享到一个节点。
蜂群学习建立在区块链之上。区块链提供了去中心化控制、可扩展性和容错性等方面,以超越单个企业的限制。同时,区块链引入了一个防篡改的加密货币框架,参与者可以使用该框架来货币化他们的贡献。
BAFFLE
此外,还有一个名为BAFFLE的框架,代表基于区块链的聚合器免费联邦学习(https://arxiv.org/abs/1909.07452)。BAFFLE 也是一个无聚合器、由区块链驱动的 FL 框架,本质上具有去中心化特性。BAFFLE 利用区块链框架中的智能合约(SCs)来协调轮次管理,以及 FL 的模型聚合和更新任务。使用 BAFFLE 可以提升计算性能。全局模型也被分解成许多数据块集合,直接由 SC 处理。
现在我们已经了解了 FL 领域的最新研究和发展,在下一节中,让我们看看人工智能、科学和集体智能技术的更具有前瞻性的方面。
前往集体智能之旅
大数据已经成为人工智能运动的一个变革者。虽然边缘和人们生成数据的数量将以指数级增长,但从中提取的智能对社会有益。因此,大数据时代将逐渐将接力棒传递给由 FL(联邦学习)赋能的集体智能时代,在这个时代,人们将共同创造一个由智慧驱动的世界。
让我们从定义一个以智能为中心的时代开始,在这个时代,基于 FL 的集体智能概念得以实现。
以集体智能为中心的时代
集体智能(CI)是指大量单个实体以看似智能的方式共同行动的概念。CI 是一种涌现现象,其中人群处理信息以获得仅靠单个成员无法理解的见解。
最近,麻省理工学院集体智能中心负责人、最初提出“集体智能”一词的人托马斯·马龙,扩展了 CI 的定义:“CI 是能够从包括人和计算机在内的群体中产生的东西。CI 是一个非常普遍的特性,超级智能可以在许多类型的系统中出现,尽管我主要讨论的系统是涉及人和计算机的系统。”(参考:www2.deloitte.com/xe/en/insights/focus/technology-and-the-future-of-work/human-and-machine-collaboration.html)。
我们现在正迎来由 FL(强化学习)赋能的技术中 CI(集体智能)的新视角。
在当前的技术世界中,数据是提取智能的巨大来源。全球分散的数据集可以通过 AI 技术转化为智能集合。正如提到的,当前的趋势是大数据,因此大数据公司不仅领导着技术产业,也领导着整个世界经济。未来正朝着 CI 的方向发展。随着复杂 ML 算法(包括深度学习)的出现,CI 的愿景变得更加清晰,因为 ML 模型所代表的智能可以从人、计算机或任何生成有意义数据的设备中提取智能。
为什么 FL 会推崇 CI 的概念?FL 的本质是收集一组分布式智能,通过书中讨论的聚合机制进行增强。这本身就能实现一个无需直接从人或设备收集数据的数据无平台。
尽管书中讨论了大数据问题,但我们并没有专注于以数据为中心的平台。然而,学习大数据对于真正创建真正有价值并能在世界许多领域创造实际价值的应用系统来说,是非常关键和不可避免的。这就是为什么即使面对隐私法规、安全、数据孤岛等重大挑战,大数据领域仍然是发展最繁荣的行业。
现在是进一步发展和传播 FL 等技术的时机,这些技术可以通过从根本上解决大数据问题来加速 CI 时代的到来。这样,我们就能实现一个由真正的数学基础支持的 CI 驱动的新技术时代。
正如所述,以数据为中心的平台是当前的趋势。因此,许多数据和自动机器学习供应商可以通过组织数据和学习程序来支持并自动化创建基于机器学习的智能的过程。一个以智能为中心或以模型为中心的平台应该是下一波技术浪潮,人们可以在其中共享和增强他们自己生成的智能。借助 FL,我们甚至可以实现众包学习,人们可以共同且持续地提升机器学习模型的质量和性能。因此,FL 是智能中心平台实现智慧驱动世界的关键和必要部分。
智能互联网
物联网演变为万物互联。然而,人们真正需要的信息是什么?仅仅是大数据吗?还是从数据中提取的智能?随着 5G 技术的出现,大量数据可以在互联网上以更高的速度传输,部分解决了许多人工智能应用中的延迟问题。FL 可以交换比原始数据更少的信息,但仍需要在互联网上传输机器学习模型。
虽然许多研究项目正在最小化 FL 中的通信延迟,但在未来,与智能相关的信息将成为另一种经常在网络上交换的实体。到处都会有模型库,例如模型动物园,由 FL 支持的众包学习将更常见,以在互联网上与全球的人们共同创造更好的智能。
这种范式转变不仅限于人工智能领域本身,还包括广泛的信息技术领域。正如我们将在下一节中讨论的,这种智能互联网运动将成为众包学习和 CI 的基础,并有助于在未来的几年内让尽可能多的人获得智能。
FL 的众包学习
FL 执行的智能集合自然使其非常适合向 CI(集体智能)迈进。同样的事情也适用于人们可以集体贡献训练过程以供全球机器学习模型使用的场景。
计算机视觉和自然语言处理等领域的高性能机器学习模型已被某些大数据公司训练,通常花费巨额资金,包括数亿美元。
是否有办法集体训练一个可能对广大公众都有益的机器学习模型?借助 FL(联邦学习)的先进框架,这是可能的。
FL 提供了一种管理来自各种分布式代理的多个训练模型聚合的真正方式。在这种情况下,分布式代理本身可能是全球的每个人,其中每个机器学习模型的用户和训练者都有自己的独特数据集,这些数据集由于数据隐私、孤岛效应等原因而无法被其他人获取。
这种利用 CI 的方式通常被称为众包学习。然而,传统的众包学习是以一种更加有限的方式进行,仅仅基于大规模促进和招募数据标注者。
在 FL 的新范式下,CI 平台上的用户可以访问和下载他们感兴趣的人工智能模型,并在必要时重新训练它们,以在自己的环境中吸收学习。然后,通过用户共享训练好的机器学习模型的框架,FL 的高级聚合框架可以挑选出合适的模型进行联邦学习,使全局模型表现更佳,采用只有用户才能访问的多样化数据。
这样,通过机器学习的智能正变得更加普遍,不仅限于那些拥有大量数据和预算来训练真实机器学习模型的特定公司。换句话说,没有 FL 框架,协作学习是困难的、棘手的,甚至几乎不可能自动化。这种机器学习模型的开放性将推动整个技术世界达到下一个水平,并将使更多应用成为可能,这些应用拥有由爱好者训练的真正强大的智能,旨在使世界变得更好。
摘要
在本书的最后一章中,我们讨论了未来趋势和发展,其中 FL 预计将在未来十年内发挥关键作用。在未来,FL 对于大多数企业和应用提供商来说,将是从“希望拥有”的框架转变为“必须拥有”的技术,因为不可避免的数据隐私法规和技术趋势要求与众多用户一起实现可扩展性。
正如我们所讨论的,未来的技术将由智能互联网的概念所驱动,人们和计算机主要交换他们的智慧,共同创造一个更加智能的社会和世界。最后,由于当前与 CI 相关的协作学习趋势,以数据为中心的技术将逐渐演变为以智能为中心的技术,这使得人们高度重视 FL 相关的技术,这些技术的基石在本书中进行了讨论。
这本书是在人工智能带来的新进步时代曙光下撰写的。在过去几十年里,我们在利用大数据策略方面取得了巨大进步,但我们已经超越了这些方法,必须采用新的做事方式、新技术和新理念来继续前进。只要我们抓住当前的时刻,投资于如 FL 等新技术,我们面前就有光明的未来。
进一步阅读
如果您希望深入了解本章讨论的一些概念,以下是一些参考资料:
- 理解联邦学习的类型,由 OpenMinded 发布:
blog.openmined.org/federated-learning-types
)
- Thapa, Chandra, 等人。SplitFed: 当联邦学习遇到分割学习,AAAI 人工智能会议论文集。第 36 卷。第 8 期。2022 年:
arxiv.org/pdf/2004.12088.pdf
)
- SWARM LEARNING: 将您的分布式数据转化为竞争优势, 技术白皮书:
www.labs.hpe.com/pdf/Swarm_Learning.pdf
)
- Paritosh Ramanan 和 Kiyoshi Nakayama。BAFFLE: 基于区块链的聚合器免费联邦学习,2020 IEEE 国际区块链会议(Blockchain)。IEEE,2020 年:
arxiv.org/pdf/1909.07452.pdf
附录:探索内部库
在第四章,使用 Python 实现联邦学习服务器,和第五章,联邦学习客户端实现,都关于提供的 simple-fl GitHub 仓库中 fl_main/lib/util 目录的实现。
在本附录中,我们将提供内部库和利用类及函数的概述,并附上代码示例以展示其功能。
在本章中,我们将涵盖以下主要内容:
-
FL 系统内部库概述
-
用于实现 FL 系统的枚举类
-
理解通信处理器功能
-
理解数据结构处理器类
-
理解辅助和支持库
-
用于生成通信负载的消息传递器
技术要求
本章中介绍的所有库代码文件都可以在 GitHub 仓库(https://github.com/tie-set/simple-fl)的 fl_main/lib/util 目录中找到。
重要提示
您可以使用这些代码文件用于个人或教育目的。请注意,我们不会支持商业部署,并且不对使用代码造成的任何错误、问题或损害负责。
FL 系统内部库概述
图 A.1 展示了位于 fl_main 目录下的 lib/util 文件夹中内部库的 Python 代码组件,这些组件用于 FL 系统的数据库、聚合器和代理:

图 A.1 – 用于数据库、聚合器和代理通信的 Python 软件组件
以下是对 FL 系统中位于 lib/util 文件夹内部库的 Python 文件的简要描述。
states.py
位于 lib/util 文件夹中的 states.py 文件定义了多种枚举类以支持实现 FL 系统。类定义包括 FL 客户端状态、ML 模型类型和消息类型,以及各种消息的信息和值的位置。
communication_handler.py
在lib/util文件夹中的communication_handler.py文件可以提供数据库、FL 服务器和客户端之间的通信功能,主要定义它们之间的send和receive函数。此外,它还提供了启动数据库、聚合器和代理服务器的函数。
data_struc.py
在lib/util文件夹中的data_struc.py文件定义了一个名为LimitedDict的类,以支持 FL 周期的聚合过程。它提供了将具有字典格式的 ML 模型转换为LimitedDict以及相反的函数。
helpers.py
在lib/util文件夹中的helpers.py文件包含了一系列内部辅助函数,例如读取配置文件、生成唯一的哈希 ID、将 ML 模型打包成字典、加载和保存本地 ML 模型、获取机器的 IP 地址以及操作 FL 客户端状态。
messengers.py
在lib/util文件夹中的messengers.py文件用于生成 FL 系统之间作为通信有效载荷的各种消息,以促进书中讨论的简单 FL 系统通信协议的实现。
既然我们已经讨论了 FL 系统内部库的概述,那么接下来让我们更详细地讨论各个代码文件。
用于实现 FL 系统的枚举类
枚举类用于辅助实现 FL 系统。它们定义在fl_main目录的lib/util文件夹中的states.py文件中。让我们看看定义枚举类时导入了哪些库。
导入库以定义枚举类
在这个states.py代码示例中,该文件导入了来自enum的通用库,如Enum和IntEnum:
from enum import Enum, IntEnum
接下来,我们将解释定义 FL 系统三个组成部分前缀的类。
定义 FL 系统组件的 IDPrefix
以下是一系列定义 FL 系统组件的类。IDPrefix是用于指示代码中引用的 FL 组件的前缀,例如agent、aggregator或database:
class IDPrefix:
agent = 'agent'
aggregator = 'aggregator'
db = 'database'
接下来,我们将提供客户端状态的类列表。
客户端状态类
以下是与 FL 客户端状态相关的枚举类列表,包括等待全局模型的状态(waiting_gm)、机器学习训练的状态(training)、发送本地机器学习模型的状态(sending)和接收全局模型的状态(gm_ready)。在代理规范中定义的客户端状态如下:
# CLIENT STATE
class ClientState(IntEnum):
waiting_gm = 0
training = 1
sending = 2
gm_ready = 3
定义 ML 模型和消息类型的类列表
以下是一系列定义与 FL 系统实现相关的 ML 模型和消息类型的类。
ModelType 类
机器学习模型的类型,包括local模型和cluster模型(global模型),定义如下:
class ModelType(Enum):
local = 0
cluster = 1
DBMsgType 类
消息类型在聚合器和数据库之间的通信协议中定义,如下所示:
class DBMsgType(Enum):
push = 0
AgentMsgType 类
消息类型在从代理发送到聚合器的通信协议中定义,如下所示:
class AgentMsgType(Enum):
participate = 0
update = 1
polling = 2
AggMsgType 类
消息类型在从聚合器发送到代理的通信协议中定义,如下所示:
class AggMsgType(Enum):
welcome = 0
update = 1
ack = 2
定义消息位置的州类列表
以下是与 FL 系统之间通信相关的消息位置定义的类列表。
ParticipateMSGLocation 类
从代理到聚合器的参与消息的索引指示器如下:
class ParticipateMSGLocation(IntEnum):
msg_type = 0
agent_id = 1
model_id = 2
lmodels = 3
init_flag = 4
sim_flag = 5
exch_socket = 6
gene_time = 7
meta_data = 8
agent_ip = 9
agent_name = 10
round = 11
ParticipateConfirmationMSGLocation 类
从聚合器返回的参与确认消息的索引指示器如下:
class ParticipateConfirmationMSGLocation(IntEnum):
msg_type = 0
aggregator_id = 1
model_id = 2
global_models = 3
round = 4
agent_id = 5
exch_socket = 6
recv_socket = 7
DBPushMsgLocation 类
从聚合器到数据库的push消息的索引指示器如下:
class DBPushMsgLocation(IntEnum):
msg_type = 0
component_id = 1
round = 2
model_type = 3
models = 4
model_id = 5
gene_time = 6
meta_data = 7
req_id_list = 8
GMDistributionMsgLocation 类
从聚合器到代理的全局模型分布消息的索引指示器如下:
class GMDistributionMsgLocation(IntEnum):
msg_type = 0
aggregator_id = 1
model_id = 2
round = 3
global_models = 4
ModelUpMSGLocation 类
从代理到聚合器上传本地机器学习模型的索引指示器如下:
class ModelUpMSGLocation(IntEnum):
msg_type = 0
agent_id = 1
model_id = 2
lmodels = 3
gene_time = 4
meta_data = 5
PollingMSGLocation 类
从代理到聚合器的polling消息的索引指示器如下:
class PollingMSGLocation(IntEnum):
msg_type = 0
round = 1
agent_id = 2
我们已定义了在整个 FL 系统代码中使用的枚举类。在下一节中,我们将讨论通信处理器的功能。
理解通信处理器功能
通信处理器的功能实现在communication_handler.py文件中,该文件位于fl_main目录下的lib/util文件夹中。
为通信处理器导入库
在此communication_handler.py代码示例中,处理器导入了一般库,如websockets、asyncio、pickle和logging:
import websockets, asyncio, pickle, logging
接下来,我们将提供通信处理器功能的列表。
通信处理器的功能
以下是与通信处理器相关的函数列表。尽管为了简化,此处未在通信处理器代码中实现安全套接字层(SSL)或传输层安全性(TLS)框架,但建议始终支持它们以保护 FL 组件之间的通信安全。
init_db_server 函数
init_db_server函数用于在 FL 服务器端启动数据库服务器。它接受一个函数、数据库 IP 地址和套接字信息作为输入,并根据 WebSocket 框架启动服务器功能。您也可以使用其他通信协议,如 HTTP。以下是启动数据库服务器的示例代码:
def init_db_server(func, ip, socket):
start_server = websockets.serve( \
func, ip, socket, max_size=None, max_queue=None)
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
loop.run_forever()
init_fl_server 函数
init_fl_server 函数用于在聚合器端启动 FL 服务器。它接受三个函数作为参数,用于代理注册、从代理接收消息和模型合成例程,以及聚合器的 IP 地址和注册接收套接字信息(用于接收来自代理的消息),以基于 WebSocket 框架启动服务器功能。以下是启动 FL 服务器的示例代码:
def init_fl_server(register, receive_msg_from_agent, \
model_synthesis_routine, aggr_ip, \
reg_socket, recv_socket):
loop = asyncio.get_event_loop()
start_server = websockets.serve(register, aggr_ip, \
reg_socket, max_size=None, max_queue=None)
start_receiver = websockets.serve( \
receive_msg_from_agent, aggr_ip, recv_socket, \
max_size=None, max_queue=None)
loop.run_until_complete(asyncio.gather( \
start_server, start_receiver, \
model_synthesis_routine))
loop.run_forever()
init_client_server 函数
init_client_server 函数用于启动 FL 客户端服务器功能。它接受一个函数、代理的 IP 地址以及用于接收来自聚合器的消息的套接字信息作为输入,并根据 WebSocket 框架启动功能。以下是启动 FL 客户端服务器功能的示例代码:
def init_client_server(func, ip, socket):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
client_server = websockets.serve(func, ip, socket, \
max_size=None, max_queue=None)
loop.run_until_complete(asyncio.gather(client_server))
loop.run_forever()
发送函数
send 函数用于将消息发送到由 IP 地址和套接字信息指定的目的地,这些信息作为参数一起传递,同时传递要发送的消息。如果目的地节点有响应,则返回从目的地节点发送回源节点的响应消息:
async def send(msg, ip, socket):
resp = None
try:
wsaddr = f'ws://{ip}:{socket}'
async with websockets.connect( \
wsaddr, max_size=None, max_queue=None, \
ping_interval=None) as websocket:
await websocket.send(pickle.dumps(msg))
try:
rmsg = await websocket.recv()
resp = pickle.loads(rmsg)
except:
pass
return resp
except:
return resp
send_websocket 函数
send_websocket 函数用于将消息返回给由 WebSocket 信息指定的消息源,该信息作为参数一起传递,同时传递要发送的消息:
async def send_websocket(msg, websocket):
while not websocket:
await asyncio.sleep(0.001)
await websocket.send(pickle.dumps(msg))
接收函数
receive 函数用于接收一个作为参数的 WebSocket 消息,并返回一个序列化的消息:
async def receive(websocket):
return pickle.loads(await websocket.recv())
接下来,我们将讨论处理 ML 模型的数据结构类。
理解数据结构处理程序类
数据结构处理程序在 data_struc.py 文件中实现,该文件位于 fl_main 目录的 lib/util 文件夹中。数据结构类具有 LimitedDict 类,用于以一致的方式处理 ML 模型的聚合。
导入数据结构处理程序的库
在这个 data_struc.py 代码示例中,处理程序导入了通用库,如 numpy 和 Dict:
from typing import Dict
import numpy as np
接下来,让我们继续了解 LimitedDict 类及其与数据结构处理程序相关的函数。
LimitedDict 类
以下是对 LimitedDict 类及其与数据结构处理程序相关的函数的定义。
LimitedDict 类及其函数
LimitedDict 类的函数用于将字典格式转换为具有键和值的类。LimitedDict 与 ML 模型中的缓冲区一起使用,以存储聚合器状态管理器的内存空间中的本地和集群模型:
class LimitedDict(dict):
def __init__(self, keys):
self._keys = keys
self.clear()
def __setitem__(self, key, value):
if key not in self._keys:
raise KeyError
dict.__setitem__(self, key, value)
def clear(self):
for key in self._keys:
self[key] = list()
convert_LDict_to_Dict 函数
convert_LDict_to_Dict 函数用于将之前定义的 LimitedDict 实例转换为正常字典格式:
def convert_LDict_to_Dict(ld: LimitedDict)
-> Dict[str,np.array]:
d = dict()
for key, val in ld.items():
d[key] = val[0]
return d
在下一节中,我们将讨论辅助和支持库。
理解辅助和支持库
辅助和支持函数在 helpers.py 文件中实现,该文件位于 fl_main 目录的 lib/util 文件夹中。
导入辅助库的库
在这个 helpers.py 代码示例中,文件导入了通用库,如 json 和 time:
import json, time, pickle, pathlib, socket, asyncio
from getmac import get_mac_address as gma
from typing import Dict, List, Any
from hashlib import sha256
from fl_main.lib.util.states import IDPrefix, ClientState
接下来,让我们继续到辅助库的函数列表。
辅助库的函数
以下是与辅助库相关的函数列表。
set_config_file 函数
set_config_file 函数接受配置文件的类型作为参数,例如 db、aggregator 或 agent,并返回配置文件的路径字符串:
def set_config_file(config_type: str) -> str:
# set the config file name
module_path = pathlib.Path.cwd()
config_file = \
f'{module_path}/setups/config_{config_type}.json'
return config_file
read_config 函数
read_config 函数读取 JSON 配置文件以设置数据库、聚合器或代理。它接受配置路径作为参数,并以字典格式返回配置信息:
def read_config(config_path: str) -> Dict[str, Any]:
with open(config_path) as jf:
config = json.load(jf)
return config
generate_id 函数
generate_id 函数基于 MAC 地址和实例化时间,使用哈希函数 (sha256) 生成一个系统范围内的唯一 ID,并将哈希值作为 ID:
def generate_id() -> str:
macaddr = gma()
in_time = time.time()
raw = f'{macaddr}{in_time}'
hash_id = sha256(raw.encode('utf-8'))
return hash_id.hexdigest()
generate_model_id 函数
generate_model_id 函数基于以下内容为一系列模型生成系统范围内的唯一 ID:
-
组件 ID:创建模型的 FL 系统实体的 ID
-
生成时间:模型的创建时间
ID 是通过哈希函数(sha256)生成的。它接受以下参数:
-
component_type:带有前缀的字符串值,指示IDPrefix的组件类型 -
component_id:创建模型的实体的 ID 字符串值 -
gene_time:模型创建时间的浮点值
此函数将哈希值作为模型 ID 返回:
def generate_model_id(component_type: str, \
component_id: str, gene_time: float) -> str:
raw = f'{component_type}{component_id}{gene_time}'
hash_id = sha256(raw.encode('utf-8'))
return hash_id.hexdigest()
create_data_dict_from_models 函数
create_data_dict_from_models 函数通过以下参数创建 ML 模型的数据字典:
-
model_id:模型 ID 的字符串值 -
models:关于 ML 模型的np.array -
component_id:FL 系统的 ID,例如聚合器 ID 和代理 ID
它返回包含 ML 模型的数据字典:
def create_data_dict_from_models( \
model_id, models, component_id):
data_dict = dict()
data_dict['models'] = models
data_dict['model_id'] = model_id
data_dict['my_id'] = component_id
data_dict['gene_time'] = time.time()
return data_dict
create_meta_data_dict 函数
create_meta_data_dict 函数通过将性能指标 (perf_val) 和样本数量 (num_samples) 作为参数,创建包含 ML 模型元数据的元数据字典,并返回 meta_data_dict,其中包含性能值和样本数量:
def create_meta_data_dict(perf_val, num_samples):
meta_data_dict = dict()
meta_data_dict["accuracy"] = perf_val
meta_data_dict["num_samples"] = num_samples
return meta_data_dict
compatible_data_dict_read 函数
compatible_data_dict_read 函数接受 data_dict,其中包含与 ML 模型相关的信息,如果字典中存在相应的键,则提取值,并返回组件 ID、ML 模型的生成时间、ML 模型本身和模型 ID:
def compatible_data_dict_read(data_dict: Dict[str, Any])
-> List[Any]:
if 'my_id' in data_dict.keys():
id = data_dict['my_id']
else:
id = generate_id()
if 'gene_time' in data_dict.keys():
gene_time = data_dict['gene_time']
else:
gene_time = time.time()
if 'models' in data_dict.keys():
models = data_dict['models']
else:
models = data_dict
if 'model_id' in data_dict.keys():
model_id = data_dict['model_id']
else:
model_id = generate_model_id( \
IDPrefix.agent, id, gene_time)
return id, gene_time, models, model_id
save_model_file 函数
save_model_file 函数用于将一组模型保存到本地文件。它接受以下参数:
-
data_dict:包含模型 ID 和 ML 模型的字典,格式为Dict[str,np.array]。 -
path:ML 模型存储目录的路径字符串值 -
name:模型文件名的字符串值。 -
performance_dict:一个包含性能数据的字典,格式为Dict[str,float]。每个条目都包含模型 ID 和其性能信息:
def save_model_file(
data_dict: Dict[str, Any], path: str, name: str,
performance_dict: Dict[str, float] = dict()):
data_dict['performance'] = performance_dict
fname = f'{path}/{name}'
with open(fname, 'wb') as f:
pickle.dump(data_dict, f)
The load_model_file 函数
load_model_file 函数读取一个本地模型文件,需要以下参数:
-
path:存储机器学习模型的目录的字符串值 -
name:模型文件名的字符串值
它以 Dict 格式返回未反序列化的机器学习模型和性能数据:
def load_model_file(path: str, name: str) \
-> (Dict[str, Any], Dict[str, float]):
fname = f'{path}/{name}'
with open(fname, 'rb') as f:
data_dict = pickle.load(f)
performance_dict = data_dict.pop('performance')
# data_dict only includes models
return data_dict, performance_dict
The read_state 函数
read_state 函数读取一个本地状态文件,需要以下参数:
-
path:客户端状态文件目录的字符串值 -
name:模型文件名的字符串值
此函数返回一个客户端状态,ClientState(例如,training 或 sending),文件中指示的状态,以整数格式。如果客户端状态文件在访问时正在写入,它将在 0.01 秒后再次尝试读取文件:
def read_state(path: str, name: str) -> ClientState:
fname = f'{path}/{name}'
with open(fname, 'r') as f:
st = f.read()
if st == '':
time.sleep(0.01)
return read_state(path, name)
return int(st)
The write_state 函数
write_state 函数用于在代理的状态文件上更改客户端状态。它需要以下参数:
-
path:客户端状态文件目录的字符串值 -
name:模型文件名的字符串值 -
state:ClientState的值(例如,training 或 sending),用于设置新的客户端状态:
def write_state(path: str, name: str, state: ClientState):
fname = f'{path}/{name}'
with open(fname, 'w') as f:
f.write(str(int(state)))
The get_ip 函数
The get_ip 函数获取机器的 IP 地址并返回 IP 地址的值:
def get_ip() -> str:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('1.1.1.1', 1))
ip = s.getsockname()[0]
except:
ip = '127.0.0.1'
finally:
s.close()
return ip
The init_loop 函数
The init_loop 函数用于启动一个连续循环函数。它需要一个用于运行循环函数的函数:
def init_loop(func):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(asyncio.gather(func))
loop.run_forever()
在下一节中,让我们看看创建通信有效载荷的消息传递函数。
用于生成通信有效载荷的消息传递器
消息传递函数定义在 messengers.py 文件中,该文件位于 fl_main 目录的 lib/util 文件夹中。
为消息传递导入库
在这个 messengers.py 代码示例中,文件导入了通用库,如 time 和 numpy。它还导入了 ModelType、DBMsgType、AgentMsgType 和 AggMsgType,这些在本书的 实现联邦学习系统的枚举类 部分中定义:
import time
import numpy as np
from typing import Dict, List, Any
from fl_main.lib.util.states import \
ModelType, DBMsgType, AgentMsgType, AggMsgType
接下来,让我们继续查看 messengers 库的函数列表。
消息传递函数的功能
以下是与 messengers 库相关的函数列表。
The generate_db_push_message 函数
generate_db_push_message 函数生成并返回一个消息,用于将包含机器学习模型的推送消息推送到数据库。它通过以下参数将它们打包为有效载荷消息(以 List 格式,消息类型定义为 push),在聚合器和数据库之间:
-
component_id:组件 ID 的字符串值,例如聚合器 ID -
round:FL 轮次信息,以整数格式 -
model_type:机器学习模型类型,例如cluster或local模型 -
models:格式为Dict[str, np.array]的机器学习模型 -
model_id: 机器学习模型唯一 ID 的字符串值 -
gene_time: 机器学习模型生成时间的浮点值 -
performance_dict: 以Dict[str, float]格式的性能数据
以下代码提供了生成前面数据库推送消息的功能:
def generate_db_push_message(
component_id: str, round: int, model_type: ModelType,
models: Dict[str,np.array], model_id: str,
gene_time: float, performance_dict: Dict[str,float])
-> List[Any]:
msg = list()
msg.append(DBMsgType.push) # 0
msg.append(component_id) # 1
msg.append(round) # 2
msg.append(model_type) # 3
msg.append(models) # 4
msg.append(model_id) # 5
msg.append(gene_time) # 6
msg.append(performance_dict) # 7
return msg
The generate_lmodel_update_message function
generate_lmodel_update_message 函数生成并返回一个消息,用于向聚合器发送包含在代理中创建的本地模型的消息。它通过以下参数将它们打包为有效载荷消息(以 List 格式,消息类型定义为 update),在代理和聚合器之间传递:
-
agent_id: 代理 ID 的字符串值 -
model_id: 机器学习模型唯一 ID 的字符串值 -
local_models: 以Dict[str, np.array]格式的本地机器学习模型 -
performance_dict: 以Dict[str, float]格式的性能数据
以下代码展示了生成前面本地模型更新消息的功能:
def generate_lmodel_update_message(
agent_id: str, model_id: str,
local_models: Dict[str,np.array],
performance_dict: Dict[str,float]) -> List[Any]:
msg = list()
msg.append(AgentMsgType.update) # 0
msg.append(agent_id) # 1
msg.append(model_id) # 2
msg.append(local_models) # 3
msg.append(time.time()) # 4
msg.append(performance_dict) # 5
return msg
generate_cluster_model_dist_message 函数
generate_cluster_model_dist_message 函数生成并返回一个 List 格式的消息,用于发送包含由聚合器创建的全局模型的消息到连接的代理。它通过以下参数将它们打包为有效载荷消息(以 List 格式,消息类型定义为 update),在聚合器和代理之间传递:
-
aggregator_id: 聚合器 ID 的字符串值 -
model_id: 机器学习模型唯一 ID 的字符串值 -
round: 以整数格式表示的联邦学习轮次信息 -
models: 以Dict[str, np.array]格式的机器学习模型
以下代码展示了生成前面聚类模型分布消息的功能:
def generate_cluster_model_dist_message(
aggregator_id: str, model_id: str, round: int,
models: Dict[str,np.array]) -> List[Any]:
msg = list()
msg.append(AggMsgType.update) # 0
msg.append(aggregator_id) # 1
msg.append(model_id) # 2
msg.append(round) # 3
msg.append(models) # 4
return msg
generate_agent_participation_message 函数
generate_agent_participation_message 函数生成并返回一个消息,用于发送包含由代理创建的初始模型参与请求消息到连接的聚合器。它通过以下参数将它们打包为有效载荷消息(以 List 格式,消息类型定义为 participate),在代理和聚合器之间传递:
-
agent_name: 代理名称的字符串值 -
agent_id: 代理 ID 的字符串值 -
model_id: 机器学习模型唯一 ID 的字符串值 -
models: 以Dict[str, np.array]格式的机器学习模型 -
init_weights_flag: 一个布尔值,用于指示权重是否已初始化 -
simulation_flag: 一个布尔值,用于指示运行是否为模拟 -
exch_socket: 用于从聚合器向该代理发送消息的字符串值套接字信息 -
gene_time: 机器学习模型生成时间的浮点值 -
meta_dict: 以Dict[str, float]格式的性能数据 -
agent_ip: 代理自身的 IP 地址
以下代码展示了生成前面代理参与消息的功能:
def generate_agent_participation_message(
agent_name: str, agent_id: str, model_id: str,
models: Dict[str,np.array], init_weights_flag: bool,
simulation_flag: bool, exch_socket: str,
gene_time: float, meta_dict: Dict[str,float],
agent_ip: str) -> List[Any]:
msg = list()
msg.append(AgentMsgType.participate) # 0
msg.append(agent_id) # 1
msg.append(model_id) # 2
msg.append(models) # 3
msg.append(init_weights_flag) # 4
msg.append(simulation_flag) # 5
msg.append(exch_socket) # 6
msg.append(gene_time) # 7
msg.append(meta_dict) # 8
msg.append(agent_ip) # 9
msg.append(agent_name) # 9
return msg
generate_agent_participation_confirm_message 函数
generate_agent_participation_confirm_message 函数生成并返回一个消息,用于发送包含全局模型的参与确认消息回代理。它接受以下参数,将它们打包成一个有效载荷消息(以List格式,消息类型定义为welcome),在聚合器和代理之间:
-
aggregator_id: 聚合器 ID 的字符串值 -
model_id: 机器学习模型唯一 ID 的字符串值 -
models: 以Dict[str, np.array]格式的机器学习模型 -
round: 以整数格式表示的联邦学习轮次信息 -
agent_id: 代理 ID 的字符串值 -
exch_socket: 从聚合器到达代理的端口号 -
recv_socket: 接收来自代理的消息的端口号
以下代码展示了生成前面代理参与确认消息的功能:
def generate_agent_participation_confirm_message(
aggregator_id: str, model_id: str,
models: Dict[str,np.array], round: int,
agent_id: str, exch_socket: str, recv_socket: str)
-> List[Any]:
msg = list()
msg.append(AggMsgType.welcome) # 0
msg.append(aggregator_id) # 1
msg.append(model_id) # 2
msg.append(models) # 3
msg.append(round) # 4
msg.append(agent_id) # 5
msg.append(exch_socket) # 6
msg.append(recv_socket) # 7
return msg
generate_polling_message 函数
generate_polling_message 函数生成并返回一个消息,用于发送包含轮询信号的polling消息到聚合器。它接受以下参数,将它们打包成一个有效载荷消息(以List格式,消息类型定义为polling),在代理和聚合器之间:
-
round: 以整数格式表示的联邦学习轮次信息 -
agent_id: 代理 ID 的字符串值
以下代码展示了生成前面轮询消息的功能:
def generate_polling_message(round: int, agent_id: str):
msg = list()
msg.append(AgentMsgType.polling) # 0
msg.append(round) # 1
msg.append(agent_id) # 2
return msg
generate_ack_message 函数
generate_ack_message 函数生成并返回一个消息,用于发送包含确认信号的ack消息回代理。创建有效载荷消息(以List格式,消息类型定义为ack)不需要参数,在聚合器和代理之间:
def generate_ack_message():
msg = list()
msg.append(AggMsgType.ack) # 0
return msg
摘要
在本章中,我们详细解释了内部库,以便您可以在不进一步调查基本功能(如通信和数据结构转换框架)的情况下实现整个联邦学习系统。
内部库主要涵盖以下五个方面:枚举类,定义系统状态,例如联邦学习客户端状态;通信处理器,支持发送和接收功能;数据结构,在聚合时处理机器学习模型;辅助和支持函数,处理基本操作,如保存数据和生成随机 ID;以及messenger 函数,用于生成在数据库、聚合器和代理之间发送的各种有效载荷。
使用这些函数,您会发现联邦学习系统的实现既简单又顺畅,但这些库仅支持实现联邦学习系统的一些基本功能;因此,您需要进一步扩展联邦学习系统,以创建一个更真实的平台,可以用于现实生活中的用例和技术。


浙公网安备 33010602011771号