近期,我跟隐私计算打交道了。
最近一段时间,我除了写业务代码之外,还整了个新鲜玩意,那就是参与搞了一下隐私计算相关的事情。
现在有空了,刚好总结一下。因为隐私计算可能对于很多工程开发朋友来说很陌生,所以,我这篇文章主要是给大家科普一下,让大家知道有这么个技术以及现在的主要方向。这里面不会有太多的算法内容和底层原理,相信大家应该都能看得懂。
随着移动互联网、云计算、物联网等信息技术的蓬勃发展,世界进入了数据爆炸的"大数据时代"。在各行各业,数据都发挥着至关重要的作用,越来越多的场景需要多方数据的流通和共享。如我们所在的金融部门,则需要借助外部金融数据,结合我们的场景业务数据进行联合建模,实现联合风控、数字营销、智能反诈、精准获客等。
所以,站在这个历史节点上,在数据合作、共享方面,横亘在我们面前的有几个重要的问题需要解决:
1、“数据孤岛“现象普遍存在;数据流通安全性风险高;
2、数据合规监管日趋严格;隐私泄露导致信任鸿沟;
其中,随着个人信息保护法在2021年11月的推出,监管问题更是我们亟待解决的。
但是近几年,随着电子商务法、数据安全法、《个人信息保护法》等一些列法律法规的推出,使得我们不得不重视个人隐私的问题。
虽然个人信息的保护越来越严格,但是,从很多的法规中我们可以解读出来,其实,大的形式上,官方还是比较支持我们合规的进行数据的利用和开发的。
那么,如何解决这些问题呢?
其实,从《个保法》中我们可以找到一些切入口,根据中对个人信息的定义是这样的:
以电子或者其他方式记录的与已识别或者可识别的自然人有关的各种信息,不包括匿名化处理后的信息。
可见,如果我们能够对个人信息进行去标识化和匿名化处理,那么,就可以利用这些信息了。
所以,很多相关的技术也就诞生了,这些技术主要解决的就是通过数据可用不可见的方式帮助我们进行跨机构间的数据协同。这类技术,统称为隐私计算技术。
隐私保护计算经过很多年的发展,在具体的落地实现的技术上,主要有三个主流的方向:
1、基于密码学的多方安全计算(MPC) 2、基于可信硬件的可信执行环境(TEE) 3、基于混合技术方案的联邦学习(FL)
想要实现去标识化和匿名化,主要的方式就是让原始的用户数据无法被识别。
其中多方安全计算主要是基于密码学的方式,把数据进行加密处理,这样我们就可以使用加密后的数据进行算法建模了。
另外,可信执行环境是一种基于硬件的数据,大概就是把数据放到一个硬件中,只在这个硬件内部使用,外部无法直接读取。
还有一种技术那就是混合了多种方案的技术,那就是联邦学习。
其中联邦学习因为其不依赖硬件、可以解决复杂的算法建模问题等优势,虽然相比其他方案存在着一定的效率问题,但是随着技术的发展,如何突破性能瓶颈,达到实用性、安全性的平衡,并进一步提升安全性,这些问题终将被解决。所以,这项技术被认为是"人工智能的最后一公里"、"下一代人工智能协同算法和协作网络的基础"。
联邦学习
联邦学习(Federated Learning)是一种新兴的人工智能基础技术,在 2016 年由谷歌最先提出,原本用于解决安卓手机终端用户在本地更新模型的问题,其设计目标是在保障大数据交换时的信息安全、保护终端数据和个人数据隐私、保证合法合规的前提下,在多参与方或多计算结点之间开展高效率的机器学习。
我们把每个参与共同建模的企业称为参与方,根据多参与方之间数据分布的不同,把联邦学习分为三类:横向联邦学习、纵向联邦学习和联邦迁移学习。
横向联邦学习的本质是样本的联合,适用于参与者间业态相同但触达客户不同,即特征重叠多,用户重叠少时的场景,比如不同地区的银行间,他们的业务相似(特征相似),但用户不同(样本不同)。主要解决样本不足的问题。
纵向联邦学习的本质是特征的联合,适用于用户重叠多,特征重叠少的场景,比如同一地区的商超和银行,他们触达的用户都为该地区的居民(样本相同),但业务不同(特征不同)。主要解决特征不足的问题。
因为我们这面主要是金融业务,隐私计算的应用场景是基于联邦学习和外部银行、机构等做联合风控,多头借贷等金融业务。所以我们基本上是想用我们具有的用户的电商数据、和外部机构具有的信贷数据、征信数据等进行纵向连邦学习。
纵向联邦学习的主要流程如下:
第一步:加密样本对齐。是在系统级做这件事,因此在企业感知层面不会暴露非交叉用户。 第二步:对齐样本进行模型加密训练: step1:由第三方C向A和B发送公钥,用来加密需要传输的数据; step2:A和B分别计算和自己相关的特征中间结果,并加密交互,用来求得各自梯度和损失; step3:A和B分别计算各自加密后的梯度并添加掩码发送给C,同时B计算加密后的损失发送给C; step4:C解密梯度和损失后回传给A和B,A、B去除掩码并更新模型。
FATE 框架
因为目前关于联邦学习的技术,很多大厂都有在投入,其中阿里、蚂蚁、字节、腾讯等都很多成功的案例。
我们此次在进行联邦学习相关调研的时候,有不同的同事分别调研不同的框架。我这面主要负责基于开源的框架进行调研。
因为关于联邦学习,最重要的还是解决数据的安全性问题,目前看来,很多外部机构,对于开源的框架接受度更高一些。市面上也有很多联邦学习框架,如微众银行开源的FATE、字节开源的FedLearner、百度开源的PaddleFL等。其中 FATE 被使用的最广泛,被认为是联邦学习的样板项目。
FATE (Federated AI Technology Enabler) 是微众银行AI部门发起的开源项目,为联邦学习生态系统提供了可靠的安全计算框架。FATE项目使用多方安全计算 (MPC) 以及同态加密 (HE) 技术构建底层安全计算协议,以此支持不同种类的机器学习的安全计算,包括逻辑回归、基于树的算法、深度学习和迁移学习等。
FATE 有4种部署方式,分别是基于Docker-Compose的部署、Standalone部署、Native的集群部署、基于KubeFATE的部署。
-
基于Docker-Compose:快速体验一下FATE,跑的模型和数据在单台机器就够了,部署起来比较简单。
-
Standalone单机部署:只是想开发算法,而开发机器性能又不高。
-
基于KubeFATE:对FATE的使用需求因数据集和模型变大,需要扩容,并且里面有数据需要维护一个FATE集群,则考虑使用基于KubeFATE在Kubernetes集群的部署方案。
-
Native的集群部署:一般是在特殊原因下才会用,如内部无法部署Kubernetes,或者需要对FATE的部署进行自己的二次开发等。 为了快速验证,我们此次部署主要是采用了基于Docker-Compose和基于KubeFATE两种部署方式。部署过程中还是遇到了很多的问题的。
关于这两种部署方式的部署过程以及一些问题的解决,不是本文的重点,我把他们单独放到我的博客中了,大家如果感兴趣可以去我的博客中阅读。
下面这张就是 FATE 的一个部署架构图:
太多的细节就不在这里深入介绍了。
我们基于 FATE,和外部机构合作搞了一套联邦学习的环境,阿里作为一方、外部机构作为另外一方,对大概十几万的数据做了联邦学习的建模。
最终结果还是比较符合我们的预期的,联邦学习的建模方式和本地建模,在性能上的损耗很小,小到几乎可忽略。
其他
以上,算是我对这段时间关于隐私计算&联邦学习的一些调研以及实践的一些总结。
之所以要研究这个,一方面是工作中需要,另外一方面,对于新技术,我们还是要多多接触了解一下。尤其是这些对于当下以及未来都是很重要的东西。
就像我在阿里内网,给自己的签名一样:不设限。
对于这部分内容,目前我刚刚接触不久,很多内容都是基于我自己的理解表达的,如文中有错误之处,欢迎大家帮忙指出。同时也欢迎有相关经验的朋友一起交流。
上一篇: 2019-04-21
推荐阅读
-
近期,我跟隐私计算打交道了。
-
F#探险之旅(二):函数式编程(上)-函数式编程范式简介 F#主要支持三种编程范式:函数式编程(Functional Programming,FP)、命令式编程(Imperative Programming)和面向对象(Object-Oriented,OO)的编程。回顾它们的历史,FP是最早的一种范式,第一种FP语言是IPL,产生于1955年,大约在Fortran一年之前。第二种FP语言是Lisp,产生于1958,早于Cobol一年。Fortan和Cobol都是命令式编程语言,它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟,至今已是最流行的编程范式。有道是“*代有语言出,各领风骚数十年”。 尽管强大的FP语言(SML,Ocaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两个)在1950年代就不断发展,FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据领先。今天,FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。 纯粹的FP将程序看作是接受参数并返回值的函数的集合,它不允许有副作用(side effect,即改变了状态),使用递归而不是循环进行迭代。FP中的函数很像数学中的函数,它们都不改变程序的状态。举个简单的例子,一旦将一个值赋给一个标识符,它就不会改变了,函数不改变参数的值,返回值是全新的值。 FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮。但它无状态和递归的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题,它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好的范式。 关于FP的更多内容建议阅读一下这篇文章:Why Functional Programming Matters(中文版)。F#中的函数式编程 从现在开始,我将对F#中FP相关的主要语言结构逐一进行介绍。标识符(Identifier) 在F#中,我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它。通过关键字let定义标识符,如: let x = 42 这看起来像命令式编程语言中的赋值语句,两者有着关键的不同。在纯粹的FP中,一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因。另外,在某些条件下,我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的。 标识符也可用于引用函数,在F#中函数本质上也是值。也就是说,F#中没有真正的函数名和参数名的概念,它们都是标识符。定义函数的方式与定义值是类似的,只是会有额外的标识符表示参数: let add x y = x + y 这里共有三个标识符,add表示函数名,x和y表示它的参数。关键字和保留字关键字是指语言中一些标记,它们被编译器保留作特殊之用。在F#中,不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是: abstract and as asr assert begin class default delegate do donedowncast downto elif else end exception extern false finally forfun function if in inherit inline interface internal land lazy letlor lsr lxor match member mod module mutable namespace new nullof open or override private public rec return sig static structthen to true try type upcast use val void when while with yield 保留字是指当前还不是关键字,但被F#保留做将来之用。可以用它们来定义标识符或类型名称,但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性,最好不要使用。它们是: atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixinobject parallel process protected pure sealed trait virtual volatile 文字值(Literals) 文字值表示常数值,在构建计算代码块时很有用,F#提供了丰富的文字值集。与C#类似,这些文字值包括了常见的字符串、字符、布尔值、整型数、浮点数等,在此不再赘述,详细信息请查看F#手册。 与C#一样,F#中的字符串常量表示也有两种方式。一是常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string),其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示。下面这个简单的例子演示了常见的文字常量表示: let message = "Hello World"r"n!" // 常规字符串let dir = @"C:"FS"FP" // 逐字字符串let bytes = "bytes"B // byte 数组let xA = 0xFFy // sbyte, 16进制表示let xB = 0o777un // unsigned native-sized integer,8进制表示let print x = printfn "%A" xlet main = print message; print dir; print bytes; print xA; print xB; main Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值,也可以通过F#中的print_any和print_to_string函数来完成类似的功能。值和函数(Values and Functions) 在F#中函数也是值,F#处理它们的语法也是类似的。 let n = 10let add a b = a + blet addFour = add 4let result = addFour n printfn "result = %i" result 可以看到定义值n和函数add的语法很类似,只不过add还有两个参数。对于add来说a + b的值自动作为其返回值,也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说,它定义在add的基础上,它只向add传递了一个参数,这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念,F(x, y) = x + y,G(y) = F(4, y),实际上G(y) = 4 + y,G也是一个函数,它接收一个参数,这个地方是不是很类似?这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。 当然对某些函数来说,传递部分参数是无意义的,此时需要强制提供所有参数,可是将参数括起来,将它们转换为元组(tuple)。下面的例子将不能编译通过: let sub(a, b) = a - blet subFour = sub 4 必须为sub提供两个参数,如sub(4, 5),这样就很像C#中的方法调用了。 对于这两种方式来说,前者具有更高的灵活性,一般可优先考虑。 如果函数的计算过程中需要定义一些中间值,我们应当将这些行进行缩进: let halfWay a b = let dif = b - a let mid = dif / 2 mid + a 需要注意的是,缩进时要用空格而不是Tab,如果你不想每次都按几次空格键,可以在VS中设置,将Tab字符自动转换为空格;虽然缩进的字符数没有限制,但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令。作用域(Scope)作用域是编程语言中的一个重要的概念,它表示在何处可以访问(使用)一个标识符或类型。所有标识符,不管是函数还是值,其作用域都从其声明处开始,结束自其所处的代码块。对于一个处于最顶层的标识符而言,一旦为其赋值,它的值就不能修改或重定义了。标识符在定义之后才能使用,这意味着在定义过程中不能使用自身的值。 let defineMessage = let message = "Help me" print_endline message // error 对于在函数内部定义的标识符,一般而言,它们的作用域会到函数的结束处。 但可使用let关键字重定义它们,有时这会很有用,对于某些函数来说,计算过程涉及多个中间值,因为值是不可修改的,所以我们就需要定义多个标识符,这就要求我们去维护这些标识符的名称,其实是没必要的,这时可以使用重定义标识符。但这并不同于可以修改标识符的值。你甚至可以修改标识符的类型,但F#仍能确保类型安全。所谓类型安全,其基本意义是F#会避免对值的错误操作,比如我们不能像对待字符串那样对待整数。这个跟C#也是类似的。 let changeType = let x = 1 let x = "change me" let x = x + 1 print_string x 在本例的函数中,第一行和第二行都没问题,第三行就有问题了,在重定义x的时候,赋给它的值是x + 1,而x是字符串,与1相加在F#中是非法的。 另外,如果在嵌套函数中重定义标识符就更有趣了。 let printMessages = let message = "fun value" printfn "%s" message; let innerFun = let message = "inner fun value" printfn "%s" message innerFun printfn "%s" message printMessages 打印结果: fun value inner fun valuefun value 最后一次不是inner fun value,因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部。递归(Recursion)递归是编程中的一个极为重要的概念,它表示函数通过自身进行定义,亦即在定义处调用自身。在FP中常用于表达命令式编程的循环。很多人认为使用递归表示的算法要比循环更易理解。 使用rec关键字进行递归函数的定义。看下面的计算阶乘的函数: let rec factorial x = match x with | x when x < 0 -> failwith "value must be greater than or equal to 0" | 0 -> 1 | x -> x * factorial(x - 1) 这里使用了模式匹配(F#的一个很棒的特性),其C#版本为: public static long Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("value must be greater than or equal to 0"); } if (n == 0) { return 1; } return n * Factorial (n - 1); } 递归在解决阶乘、Fibonacci数列这样的问题时尤为适合。但使用的时候要当心,可能会写出不能终止的递归。匿名函数(Anonymous Function) 定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名,这种函数就是所谓的匿名函数,有时称为lambda函数,这也是C#3.0的一个新特性。比如有的函数仅仅作为一个参数传给另一个函数,通常就不需要起名。在后面的“列表”一节中你会看到这样的例子。除了fun,我们还可以使用function关键字定义匿名函数,它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子: let x = (fun x y -> x + y) 1 2let x1 = (function x -> function y -> x + y) 1 2let x2 = (function (x, y) -> x + y) (1, 2) 我们可优先考虑fun,因为它更为紧凑,在F#类库中你能看到很多这样的例子。 注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。 F#系列随笔索引页面
-
计算机视觉中,究竟有哪些好用的目标跟踪算法(下)-快速变形主要因为CF是模板类方法。容易跟丢这个比较好理解,前面分析了相关滤波是模板类方法,如果目标快速变形,那基于HOG的梯度模板肯定就跟不上了,如果快速变色,那基于CN的颜色模板肯定也就跟不上了。这个还和模型更新策略与更新速度有关,固定学习率的线性加权更新,如果学习率太大,部分或短暂遮挡和任何检测不准确,模型就会学习到背景信息,积累到一定程度模型跟着背景私奔了,一去不复返。如果学习率太小,目标已经变形了而模板还是那个模板,就会变得不认识目标。(举个例子,多年不见的同学,你很可能就认不出了,而经常见面的同学,即使变化很大你也认识,因为常见的同学在你大脑里面的模型在持续更新,而多年不见就是很久不更新) 快速运动主要是边界效应(Boundary Effets),而且边界效应产生的错误样本会造成分类器判别力不够强,下面分训练阶段和检测阶段分别讨论。 训练阶段,合成样本降低了判别能力。如果不加余弦窗,那么移位样本是长这样的: 除了那个最原始样本,其他样本都是“合成”的,100*100的图像块,只有1/10000的样本是真实的,这样的样本集根本不能拿来训练。如果加了余弦窗,由于图像边缘像素值都是0,循环移位过程中只要目标保持完整那这个样本就是合理的,只有目标中心接近边缘时,目标跨越边界的那些样本是错误的,这样虽不真实但合理的样本数量增加到了大约2/3(padding= 1),即使这样仍然有1/3(3000/10000)的样本是不合理的,这些样本会降低分类器的判别能力。再者,加余弦窗也不是“免费的”,余弦窗将图像块的边缘区域像素全部变成0,大量过滤掉分类器本来非常需要学习的背景信息,原本训练时判别器能看到的背景信息就非常有限,我们还加了个余弦窗挡住了背景,这样进一步降低了分类器的判别力(是不是上帝在我前遮住了帘。不是上帝,是余弦窗)。 检测阶段,相关滤波对快速运动的目标检测比较乏力。相关滤波训练的图像块和检测的图像块大小必须是一样的,这就是说你训练了一个100*100的滤波器,那你也只能检测100*100的区域,如果打算通过加更大的padding来扩展检测区域,那样除了扩展了复杂度,并不会有什么好处。目标运动可能是目标自身移动,或摄像机移动,按照目标在检测区域的位置分四种情况来看: 如果目标在中心附近,检测准确且成功。 如果目标移动到了边界附近但还没有出边界,加了余弦窗以后,部分目标像素会被过滤掉,这时候就没法保证这里的响应是全局最大的,而且,这时候的检测样本和训练过程中的那些不合理样本很像,所以很可能会失败。 如果目标的一部分已经移出了这个区域,而我们还要加余弦窗,很可能就过滤掉了仅存的目标像素,检测失败。 如果整个目标已经位移出了这个区域,那肯定就检测失败了。 以上就是边界效应(Boundary Effets),推荐两个主流的解决边界效应的方法,但速度比较慢,并不推荐用于实时场合。
-
我近期的经历:我与隐私计算开始了一段新的关系。