建立安全的 AI-深蓝源码网


时间: 2020-09-03 00:08:26 人气: 2277 评论: 0

长文预警: 在本文中,我们将训练一个在训练期间完全加密的神经网络(在未加密的数据上进行训练)。这将为神经网络带来两个有益的特性:首先,神经网络的智能可以受到更好的保护以免被他人盗取,消除了在不安全环境下训练的有价值的AI被其他智能盗取的风险;其次,网络可以只作加密的预测(这意味着在没有密钥的情况下,外界无法理解接受预测,从而网络对外界不会产生影响)。这在用户和超人工智能间建立了一个非常有价值的不对等权力。试想,如果AI是经过同态加密处理的,那么从AI的角度来看,整个外部世界也是同态加密过的。而人类可以控制密钥从而决定是解锁AI自身(在外部世界发布)还是只解码AI作出的决策(似乎显得更安全)。

注: 如果你对训练加密的神经网络感兴趣,可以参考OpenMined的PySyft库

超级智能

很多人担心超人工智能总有一天会选择伤害人类。之前,史蒂芬·霍金曾呼吁建立一个新的世界“政府”来管理我们给予人工智能的能力以免其毁灭人类。这个陈述是很大胆的,我认为它反映了科学界以至全世界共同关心的问题。在本文中,我将用一些玩具性质的代码来演示针对这个问题的潜在技术解决方案。

我们的目标很朴实。我们像发明一个可以让AI变得极度聪明(聪明到可以治愈癌症,解决全球饥饿问题)的技术,不过前提是这样的智能是在人类的控制下的,即它的应用是受限的。无止境地学习固然是好的,但是知识的无限制应用却有着潜在的危险。

为了阐述这一观点,我将首先快速讲解两个振奋人心的研究领域:深度学习和同态加密。

第1部分:什么是深度学习?

深度学习可以理解为人工智能的一套自动化工具,主要借助与神经网络。作为计算机科学的领域之一,深度学习在很多智能任务的完成质量上超越了之前的技术,这促使了AI技术的繁荣。在这一背景下,在击败世界围棋冠军的DeepMind的AlphaGo中深度学习也扮演了重要角色。

问题: 神经网络是怎样学习的呢?

神经网络基于输入进行预测。它通过不断地试验来高效地做到这一点。这个过程从一个预测开始(起初很大情况下是随机的),之后通过接受到的“误差信号”来判断它的预测是高了还是低了(通常预测的输出为概率值)。在经过很多次尝试之后,网络开始具有了辨识能力。关于神经网络工作的具体细节,可以参阅A Neural Network in 11 Lines of Python

这里的重点是上面提到的误差信号。如果说网络不知道自己的预测的水平如何,它就无法进行学习,,要牢记这一点。

第2部分:什么是同态加密?

顾名思义,同态加密是一种加密形式。在不对称的情况下,可以用“公钥”将明文转化为乱码。关键的一点是,你可以用对应的“私钥”将加密后的文本再次解码为原始的明文。但是只有在你有“私钥”的情况下你才可以解码混淆后的明文(理论上)。

同态加密只是一种特殊的加密方式。它支持某些用户在不具有原有信息读权限的情况下对加密信息进行修改操作。举个例子来说,加密的数字信息可以在不解码的情况下进行乘法和加法操作。下面给出一个简单的例子:

现在同态加密的方案越来越多,每个方案都有着不同的特性。不过这还是一个成长中的领域,还有很多关键的问题仍待解决,我们后面会再回到这个问题上讨论。

现在我们先延续上面的内容进行讲解,从上图可以看出我们可以在经过同态加密的数据上进行乘法和加法操作。此外由于公钥可以进行单向加密,这使得我们可以用加密的数字和未加密的数字进行相应操作(对未加密的数据进行单向加密),正如上图中的 2 * Cypher 一样。(一些加密算法甚至不需要这样做,我们后面再谈)

第三部分:我们可以把两者结合使用吗?

深度学习和同态加密最常见的结合场景主要体现再数据隐私方面。事实证明,当数据经过同态加密之后,虽然不能够读取其中的信息,但是你仍然可以保持数据中大部分感兴趣的统计学结构。这使得人们可以在加密数据上训练模型(CryptoNets)。此外,初创对冲基金会Numer.ai加密了昂贵,专有的数据来提供给任何人训练机器学习模型来预测股票市场。通常来说,他们是不能这样做的,因为这构成了珍贵/私密数据的泄漏(而常规的加密手段又会使模型训练变得不可能)。

不过,本文要做的是一个反向的过程,即对神经网络进行加密并在解码的数据上进行训练。

一个大型神经网络从整体上看复杂度是惊人的,但是将它们分解之后也只是一些简单操作的重复而已。事实上,许多先进的神经网络通常只需要以下操作来创建:

既然这样,我们在技术上是否可以加密同态神经网络本身吗?事实证明我们可以通过一些近似做到这一点。

  • 加法 - 开箱即用
  • 乘法 - 开箱即用
  • 除法 - 开箱即用? - 乘 (1 / 被乘数) 即可
  • 减法 - 开箱即用? - 加一个负数即可
  • Sigmoid - 嗯...也许有点难
  • Tanh - 嗯...也许有点难
  • 指数 - 嗯...也许有点难

看起来我们可以很轻松地完成除法和减法,但是完成一些复杂函数则比完成加法和乘法操作困难的多。为了对深度神经网络进行同态加密,我们需要一些“秘密配方”。

第四部分:泰勒级数展开

可能你还记得高中或者大学里这部分的内容:泰勒级数允许我们使用无限项的加减乘除操作的组合来计算非线性函数。这个方法完美地解决了我们的问题!(除了需要无限项这一点)。幸运的是,如果你只能计算泰勒展开式的前几项,你也可以得到一个近似的函数。下卖弄给出了一些常用函数的泰勒级数(来源):

其中有指数函数!可以看到展开式只是一些加减乘除操作,这是我们可以完成的。同样地,我们可以像下图所示一样用Python实现我们需要的sigmoid函数的泰勒展开式(其展开式可以在Wolfram Alpha查阅)。我们可以只取前面的部分项来观察近似结果和实际结果的差距。

import numpy as np

def sigmoid_exact(x):
  return 1 / (1 + np.exp(-x))

# using taylor series
def sigmoid_approximation(x):
  return (1 / 2) + (x / 4) - (x**3 / 48) + (x**5 / 480)

for lil_number in [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]:
  
  print("\nInput:" + str(lil_number))
  print("Exact Sigmoid:" + str(sigmoid_exact(lil_number)))
  print("Approx Sigmoid:" + str(sigmoid_approximation(lil_number)))
Input:0.1
Exact Sigmoid:0.524979187479
Approx Sigmoid:0.5249791875

Input:0.2
Exact Sigmoid:0.549833997312
Approx Sigmoid:0.549834

Input:0.3
Exact Sigmoid:0.574442516812
Approx Sigmoid:0.5744425625

Input:0.4
Exact Sigmoid:0.598687660112
Approx Sigmoid:0.598688

Input:0.5
Exact Sigmoid:0.622459331202
Approx Sigmoid:0.6224609375

Input:0.6
Exact Sigmoid:0.645656306226
Approx Sigmoid:0.645662

Input:0.7
Exact Sigmoid:0.668187772168
Approx Sigmoid:0.6682043125

Input:0.8
Exact Sigmoid:0.689974481128
Approx Sigmoid:0.690016

Input:0.9
Exact Sigmoid:0.710949502625
Approx Sigmoid:0.7110426875

Input:1.0
Exact Sigmoid:0.73105857863
Approx Sigmoid:0.73125

从结果可以看到,我们只取前四项时的计算结果就已经很接近无穷项的计算结果了。完成了复杂函数的问题之后,下面我们来选择一个同态加密算法。

第5部分:选择加密算法

同态加密是一个相对较新的领域,其里程碑事件是2009年克雷格·金特里(Craig Gentry)发明了第一个完全同态加密算法。这个发现为后来的研究者提供了立足点。这一领域大部分振奋人心的研究是围绕实现同态加密的图灵完全的计算机展开的。相应地,人们需要寻求一个有效且安全的同态加密算法方案,可以根据任意的计算输入去完成相应的逻辑门操作。人们普遍的希望是可以将工作安全地移植到云上并且不用担心发送的数据被发送者以外的人监听。这是一个非常酷的想法,也取得了很大进展。

但是还有一个缺陷,就是大多数完全同态加密算法在普通计算机上通常是非常慢的(不实用)。这引发了另一个有趣的研究方向,即只做部分同态加密的运算从而减小运算量。这种方案降低了灵活程度但提升了运算速度,是计算中常用的折衷手段。

我们从这一点出发寻找加密算法。理论上我们需要的是一个在浮点运算的同态加密方案(但我们本文更多以整数为例),为什么不是二进制呢?二进制加密固然可行,但是它不仅要求完全同态加密方案的灵活性(性能损失),还需要我们去管理二进制表示和数学操作中的逻辑操作。相比之下,没有那么强大的针对浮点数定制的HE算法更为合适。

尽管我们确定了这个限制条件,但是还有很多的算法以供选择。这里有一些具有我们喜欢的特性的流行算法:

以上方案中的最优方案应当是YASHE或FV。YASHE被应用与流行的CryptoNets(加密网络)算法当中,对浮点操作的支持很好。但是这个方法很复杂,为了使本文更加简单有趣,我们选择了稍逊一筹的方案(基于整数向量的高效同态加密,安全性要差一点)。但是,你要注意即使你读本文时出现了新的同态加密算法,但是本文中的整数/浮点数加法和乘法同态加密的实现方案是通用的。我希望通过本文可以提高你对同态加密算法应用的理解以便开发出更多的同态加密算法去优化深度学习。

这个加密算法Yu, Lai, and Paylor的工作里也有一节专门提及,还有相应的代码实现,核心代码位于vhe.cpp当中。下面我们将用Python实现相应的接口并做相应讲解。你也可以选择更高层面的实现方式或者移植到其他语言或场景下,因为这套实现方案是通用的(通用的函数名称,变量名等等)。

第6部分:Python中的同态加密

首先介绍一下同态加密中的术语:

  • 明文: 未加密的数据。这也被称为“信息”(message)。在我们的例子中,这将是代表我们神经网络的数值的集合。
  • 密文: 加密后的数据。我们在密文做的数学操作会改变底层的明文,最初的图示也提到了这一点。
  • 公钥: 伪随机数序列,任何人可以用它加密数据。公钥可以被传播,因为拿到的人只能用它来加密数据(理论上)。
  • 私钥/密钥: 伪随机数序列,允许您解密通过公钥加密的数据。私钥不应该分享给他人,否则你加密的数据就会被他人解码。

以上是使用中可以移动的组件。在不同的同态加密算法中,会有一些标准变量,本文中有以下标准变量:

  • S: 代表密钥/私钥的矩阵,你需要用它来解密信息。
  • M: 公钥。你会用它来加密东西,并执行数学运算。虽然一些算法中所有的数学运算都不需要公钥,但是公钥的使用还是相当广泛。
  • c: 加密数据矢量,“密文”。
  • x: 对应于你的信息,或者说你的“明文”。有些论文使用变量“m”代替。
  • w : 用于给我们的输入信息x重新分配权重的常量值。(使其总是更大或更小一点)。我们使用这个变量来帮助调整信噪比。使信号“变大”使得它在任何给定的操作中不易受到噪声的影响。但是,如果数据太大,则会增加我们完全损坏数据的可能性。这是一个折衷后的值。
  • E/e:通常指随机噪声。在某些情况下,公钥加密的数据在加密之前被混入了噪声。混入的噪声使解密变得困难。但这也使的相同的明文在使用相同的公钥时可以产生不同的密文,这增加了密文被破解的难度。请注意,根据算法和实现,E/e可以是矢量或矩阵。在其他情况下,它也可能是指操作累积的噪声。稍后我们会进一步讨论。

与许多数学论文一样,大写字母对应于矩阵,小写字母对应于矢量,斜体小写字母对应于标量。同态加密有四种我们关心的操作:公钥/私钥对生成,单向加密,解密和数学运算。我们先从解密开始。

上面的公式描述了私钥S和我们的“明文”x之间的一般关系。下面的公式告诉了我们如何使用私钥来解密数据。注意下面的式子,e去哪了?其实从原理上来说同态加密引入噪声的目的就是使人们在没有私钥的情况下很难解密我们的数据,但是如果你拥有密钥,噪声又显得很小以至于等于或小于舍入误差。下面公式中特殊的括号表示其中的计算结果四舍五入至最近的整数。其他的加密算法可以选择不同的舍入方式。取模运算符更是无处不在,加密的过程实际上就是生成一个满足上述等式的c。如果S是一个随机矩阵,c将很难被解密。简而言之,非对称情况下,生成加密密钥只需要找到私钥的逆即可。我们用Python代码来演示以下:

import numpy as np

def generate_key(w,m,n):
    S = (np.random.rand(m,n) * w / (2 ** 16)) # proving max(S) < w
    return S

def encrypt(x,S,m,n,w):
    assert len(x) == len(S)
    
    e = (np.random.rand(m)) # proving max(e) < w / 2
    c = np.linalg.inv(S).dot((w * x) + e)
    return c

def decrypt(c,S,w):
    return (S.dot(c) / w).astype('int')

def get_c_star(c,m,l):
    c_star = np.zeros(l * m,dtype='int')
    for i in range(m):
        b = np.array(list(np.binary_repr(np.abs(c[i]))),dtype='int')
        if(c[i] < 0):
            b *= -1
        c_star[(i * l) + (l-len(b)): (i+1) * l] += b
    return c_star

def get_S_star(S,m,n,l):
    S_star = list()
    for i in range(l):
        S_star.append(S*2**(l-i-1))
    S_star = np.array(S_star).transpose(1,2,0).reshape(m,n*l)
    return S_star


x = np.array([0,1,2,5])

m = len(x)
n = m
w = 16
S = generate_key(w,m,n)

我在iPython notebook中运行这个代码后执行了以下操作(图示有相应的输出)。

关键的是下面两个操作的结果,可以看到我们对明文做的基本算术操作改变了密文底层代表的明文。很优雅的操作,不是吗?

第7部分:优化加密过程

引入: 我们再次审视一下解密的公式,如果密钥S为单位矩阵,那么密文c就只是重新加权后的明文混入一定的噪声而已,只要具有一定量的样本就可以发现。如果这段文字你不理解的话,你需要搜索一下“单位矩阵”相关的知识再回来阅读,否则下面的内容理解起来会很吃力。

上面的例子告诉了我们加密发生的过程。这项技术的发明者并没有同时确定一对独立的“公钥”和”私钥“,而是提出了“密钥转换”技术,借助这一技术可以将私钥S转换为S'。更具体地说,这种密钥转换技术生成了可以将未加密的数据(单位矩阵作为密钥)转换为加密后的数据(随机生成的,难以被猜到的密钥)的矩阵M,M就是我们的公钥!

上面一段包含的信息很多,我们再梳理一遍:

这里发生了什么...

  1. 根据上面给定的两个公式,如果密钥是单位矩阵,则信息未加密。
  2. 根据上面给定的两个公式,如果密钥是一个随机矩阵,则生成的信息是加密的。
  3. 我们可以创建一个矩阵M,将密钥从一种形式更改为另一种形式。
  4. 当矩阵M从单位矩阵转换为随机矩阵密钥时,它通过扩展完成了信息的单向加密。
  5. 因为M扮演着“单向加密”的角色,所以我们称之为“公钥”,并且可以像公钥一样分发它,因为它并不能解密代码。

到此我们不做更深的讲解,下面我们看一下这个过程如何在Python中完成:

import numpy as np

def generate_key(w,m,n):
    S = (np.random.rand(m,n) * w / (2 ** 16)) # proving max(S) < w
    return S

def encrypt(x,S,m,n,w):
    assert len(x) == len(S)
    
    e = (np.random.rand(m)) # proving max(e) < w / 2
    c = np.linalg.inv(S).
						 技术沙龙 教程文章 热点综合					

评论