首页
社区
课程
招聘
[翻译]构建安全AI——加密深度学习教程
2017-4-16 18:07 10853

[翻译]构建安全AI——加密深度学习教程

2017-4-16 18:07
10853

TLDR:在这篇博客中,我们要训练一个训练期间完全加密的神经网络(使用未加密数据)。这个神经网络将会有两个有益的特性。首先,该神经网络智被保护不受那些想要偷窃它的人的伤害,即允许在不安全的环境下训练贵重的AI而无被盗窃的风险。第二,该神经网络可以只作出加密的预测(这对外部世界可能没有什么影响,因为外部世界无法理解没有密钥的预测)。这就在用户和超级智能之间创造出一个非常重要的权利不平衡。如果这个AI被同态加密,那么在它看来,整个外部世界也被同态加密了。控制该密钥的人就可以选择解锁这个AI(将它公之于众)或者仅仅个人拥有这个AI做出的预测(似乎更安全)


当我在@iamtrask上完成一篇新博客后,通常会发推特。如果你有兴趣阅读更多的博客的话,可以在twitter关注我,在此感谢所有的反馈。


超级智能


许多人担心超级强大的AI有一天会选择伤害人类。最近,斯蒂芬·霍金呼吁新世界政府控制我们给予人工智能的能力,这样人工智就不会反过来毁灭我们。这是相当大胆的言论,我认为这反映出了科学界和社会的一种普遍担忧。在这篇博客中,就这个问题我想给出一种可能的技术解决方案,并用一些示例代码来证明该方法。


目标很简单。我们想要构建AI科技——可以变得极度聪明(聪明到可以治愈癌症,解决世界饥饿问题等等),但是它的智能被一个持有密钥的人所控制,因此智能的应用被限制了。不受限的学习固然很棒,但是那种知识的不受限制的应用可能是危险的。


为了介绍这个想法,我会非常快速地描述两个令人兴奋的研究领域:深度学习和同态加密。


Part 1:什么是深度学习?


深度学习是为了智能自动化的一套工具,主要利用神经网络。作为计算机科学的一个领域,深度学习和近来AI科技的繁荣有着密不可分的关系,因为它在许多智能任务中已经超过之前的质量记录。例如,它在DeepMind的AlphaGo系统,最近击败了世界围棋冠军李世石,中扮演了很重要的角色。


问题:神经网络如何学习?


神经网络基于输入作出预测。它学着通过试验和错误有效地作出预测。一开始它先作出一个预测(最初有很大的随机性),然后收到一个"错误信号(error signal)"——表明它预测的太高或者太低(通常可能性)。在这个循环重复几百万次甚至几千万次后,神经网络开始作出合理的预测。若想要了解更多关于此过程的细节,请看A Neural Network in 11 Lines of Python


其中的关键点就是这个错误信号。要是神经网络不被告知它的预测的好坏程度,它就无法学习。这一点很重要,牢记。


Part 2:什么是同态加密?


人如其名,同态加密是一种加密的方式。在非对称情形下,它可以使用公钥(public key)完美地将可阅读文本(明文)转换为一堆乱七八糟的东西(密文)。更重要的是,它还能使用私钥(secret key)将那些密文还原为相应的明文。理论上,除非你拥有私钥,否则你是无法破译密文的。


同态加密是加密方法中的一种特殊类型。它允许有人以特定方式修改加密的信息而不必能读懂那些信息。例如,数字可以做同态加密,比如对加密的数值可以执行乘法和加法而无需解密它们。这里有一些简单的例子。

如今,有关同态加密的方案越来越多,每种方案都有不同的特性。这是一个相对年轻的领域,有一些重要的问题还正在研究之中,但是我们以后再说。


接下来我们只讨论以下方案。基于乘法和加法同态的整数公钥加密方案,此外可以执行上面图片中的运算。更进一步,由于公钥允许“单向”加密,你甚至可以在未加密数字和(通过单向加密的)已加密数字之间执行运算,例如上面的2 * Cypher A(有些加密方案甚至连那个也不需要……但是……我们以后再说。)


Part 3:我们能将深度学习和同态加密一起使用吗?


或许深度学习和同态加密之间最常见的交点已经证明是数据隐私方面。事实证明,当你同态加密数据后,虽然你无法读懂它的含义,但是你仍然保留了大部分有趣的统计结构。这就允许人们使用加密的数据(CryptoNets)训练模型。此外,一个新成立的对冲基金Numer.ai加密了昂贵的,专有的数据,并允许任何人尝试去训练机器学习模型以此来预测股市。正常情况下他们无法这样做,因为这要放弃极其珍贵的信息(正常的加密不可能训练模型)。


但是,这篇博客将要做的是相反的事情,加密神经网络并使用未加密数据训练它。


一个神经网络,看起来惊人的复杂,实际可分解为非常小的、简单的一遍又一遍地重复的移动单元。事实上,许多最先进的神经网络只能使用下面的运算来创建:


  · 加法

  · 乘法

  · 除法

  · 减法

  · Sigmoid

  · 双曲正切(Tanh)

  · 指数运算(Exponential)


现在,问一个明显的技术性问题,我们能否同态加密神经网络?事实证明,做一些保守的近似后,这是可以做到的。


  · 加法-现成可用的

  · 乘法-现成可用的

  · 除法-现成可用的?-简单地1/乘法

  · 减法-现成可用的?-简单地加法的反面

  · Sigmoid-呃...有点难

  · 双曲正切(Tanh)-呃...有点难

  · 指数运算(Exponential)-呃...有点难


看起来似乎我们可以十分容易地得到除法和减法,但是这些更复杂的函数...好吧...比起简单的加法和乘法要复杂得多。为了同态加密一个深度神经网络,我们还需要一个秘密武器。


Part 4:泰勒级数展开


或许你可从小学的记忆中找到它。泰勒级数(Taylor Series)允许人们利用无穷级数的加、减、乘、除来计算一个复杂的(非线性)函数。这很完美!(除了无穷那部分)。幸运的是,如果你只计算了精确的泰勒级数展开的前面有限项,你仍可以得到该函数的一个精度相当高的结果。这里有一些常用的借助泰勒级数近似的函数(Source)。


等一下!这里有指数运算!别担心。指数运算只不过是重复的乘法,我们可以计算。

玩一下吧,这里有个利用泰勒级数近似计算sigmoid函数的python实现代码。(你可以在网站Wolfram Alpha中查看公式)。我们会计算此泰勒级数的前几项并看看得到的结果和真正sigmoid函数相差多少。

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

仅仅计算了该泰勒级数的前四项,我们得到的结果就非常接近sigmoid函数了。既然我们已经有了总体策略,是时候选择一种同态加密算法了。


Part 5:选择一种加密算法


同态加密是一个相对新兴的领域,主要的里程碑是Craig Gentry于2009年提出了第一个完全同态加密算法。这个里程碑事件为许多后来者创造了一个立足点。大多数围绕同态加密的令人兴奋的成果正在被用于开发图灵完备(Turing Complete)、同态加密的计算机。因此,为了一个完全同态方案而进行的探索力图找到一种可以高效并安全地计算出进行任意运算所需的各种各样的逻辑门数量的算法。总体上的希望就是人们可以将工作安全地转移到云端,而发送中的数据不存在被除了发送者之外的任何人读懂的风险。这是一个非常酷的想法,已经取得了很多进展。


但是,还存在一些缺点。通常,大多数完全同态加密方案和正常的计算相比是极其缓慢的(还不实用)。这已经引发了一个很有趣的研究方向,那就是限制运算符的数量成为部分同态的,这样至少可以执行一些运算。低灵活性但更快,这是计算中一种常见的折中方案。


这正是我们想开始看到的地方。理论上,我们想要一种操作浮点数(但是我们会安置整数,正如我们将看到的)而非二进制数的同态加密方案。二进制数可以工作,但是它不仅需要完全同态加密的灵活性(成本计算性能),我们还必须处理二进制的含义和我们想要操作的数学运算符之间的逻辑关系。一个不那么强大,为了浮点数运算量身打造的HE算法会是一个更好的匹配。


尽管有这种约束,但是仍有大量方案可供选择。这里有一些有我们喜欢的特性的方案。


  · 基于整数矢量的高效同态加密及其应用(Efficient Homomorphic Encryption on Integer Vectors and Its Applications)

  · 另一个部分同态加密(Yet Another Somewhat Homomorphic Encryption (YASHE))

  · 有点实用性的完全同态加密(Somewhat Practical Fully Homomorphic Encryption (FV))

  · 不自举的完全同态加密(Fully Homomorphic Encryption without Bootstrapping)


这里最好使用的可能是YASHE或FV。YASHE是流行的CryptoNets算法中使用的方法,对浮点数运算的支持非常好。但是,它相当的复杂。为了使这篇博客更易读和好玩些,我们将要使用的是有点低级(或许也更不安全)的高效整数矢量同态加密。但是,我认为值得注意的是当你阅读这篇博客的时候,新的HE算法正处于被开发之中,而且这篇博客中出现的观点对于任何基于整数和(或)浮点数的加法和乘法同态的方案都是通用的。如果还有什么的话,我希望能提升HE的这种应用的认识,这样更多的HE算法将会被开发出来用于深度学习的优化。


这个加密算法在Yu, Lai, and Paylor这篇文章也被广泛地提到并且在这里还有相应的实现。这个方法的主要部分在C++文件vhe.cpp中。下面我们将要穿过这个代码的一个python端口,伴随解释说明发生了什么。如果你选择实现一个更高级的方案,这也将会是有用的,因为有相当通用的主题(普遍的函数名称,变量名称等)。


Part 6:Python中的同态加密


我们从了解一些同态加密的专业术语开始吧:


  · 明文:这是你的未加密数据。也被叫做“消息”。在我们这里,这将会是一堆数字,代表我们的神经网络。

  · 密文:这是你的已加密数据。我们将对密文做一些数学运算,这会改变潜在的明文。

  · 公钥:这是一个伪随机的数字串,任何人都可用它来加密数据。把它分享给别人也没事,因为(理论上)公钥只能被用来加密数据。

  · 私钥:这是一个伪随机的数字串,你可以用它来解密那些使用公钥加密的数据。你应该并不想把私钥分享给别人,否则,他们就可以解密你的消息。


那些是主要的移动部分。它们和不同的同态加密技术中的专有标准变量名称是一致的。。在这篇博文中,它们是下面这些:


  · S:  代表私钥的矩阵。你要用它来解密东西。

  · M: 你的公钥。你会用它来加密东西和执行数学运算。一些算法并不需要公钥做所有的数学运算,但是本文使用的算法使用公钥做了相当多的事情。

  · c: 这个矢量是你的已加密数据,即密文。

  · x: 表示你的消息,即明文。有些论文使用的是"m"。

  · w: 这是一个特定的“加权”标量变量,用来重新加权输入的消息 x(使它一直变得更大或者更小)。这个变量可帮助调整 有效信号/干扰信号 的比例。扩大有效信号会使得在所有给定操作中降低干扰信号对它的影响。然而,它的值太大完全会增加损坏数据的可能性。这是一个平衡。

  · 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中运行这段代码的时候,我可以执行下面的这些运算(输出是一致的)。



关键要看的就是下面的输出。注意,我们可以对密文执行一些基本的运算,而且它相应地改变了潜在的明文。很整齐,对吧?


Part 7:优化加密


Import Lesson:再来看看这个解密公式。如果私钥 S 是单位矩阵,那么密文 c 就只是输入 x 的一个重新加权的、带点干扰信号的版本,用一些例子可以很容易发现这一点。如果这不合理,用Google搜索"单位矩阵教程"然后回来。这里有点多。


这会让我们搞清楚加密是怎样发生的。而不是明确的分配一个独立的“公钥”和“私钥”,作者提出了一个“秘钥转化”技术,他们可以用私钥 S 换出另一个私钥 S' 。进一步讲,这个私钥转换技术包括产生一个可以执行这种转化的矩阵 M 。既然 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).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 switch_key(c,S,m,n,T):
    l = int(np.ceil(np.log2(np.max(np.abs(c)))))
    c_star = get_c_star(c,m,l)
    S_star = get_S_star(S,m,n,l)
    n_prime = n + 1
    

    S_prime = np.concatenate((np.eye(m),T.T),0).T
    A = (np.random.rand(n_prime - m, n*l) * 10).astype('int')
    E = (1 * np.random.rand(S_star.shape[0],S_star.shape[1])).astype('int')
    M = np.concatenate(((S_star - T.dot(A) + E),A),0)
    c_prime = M.dot(c_star)
    return c_prime,S_prime

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

def get_T(n):
    n_prime = n + 1
    T = (10 * np.random.rand(n,n_prime - n)).astype('int')
    return T

def encrypt_via_switch(x,w,m,n,T):
    c,S = switch_key(x*w,np.eye(m),m,n,T)
    return c,S

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

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



这段代码工作的方式主要是将秘钥 S 设为单位矩阵,在它上面简单地连接一个随机矢量 T 。因此,矢量 T 实际上包含了获得秘钥的所有必要信息,即使我们仍得创造一个和矩阵 S 同样大小的矩阵来让代码正常运行。


Part 8:构建一个异或神经网络


现在我们知道了如何加密和解密消息(还有计算基础的加法和乘法),是时候开始扩大到构建一个简单的异或神经网络所需的余下操作了。技术上的神经网络不过是一系列非常简单的运算,和一些便捷功能中所需要的这些运算之间的一些组合。所以,我会描述每个所需的运算和将要使用的高级方法(基本上就是一系列加法和乘法)。然后我会向你展示代码。详细的描述请看这个。

  · 浮点数我们要做的是简单地将浮点数缩放为整型数。我们将要用整型数训练我们的神经网络,就像是用浮点数。我们的缩放比例是1000倍。0.2 × 0.5 = 0.1按比例放大后就是200 × 500 = 100000。因为我们执行了乘法运算,所以我们必须按比例缩小1000倍两次,100000 / (1000 × 1000) = 0.1才是我们想要的。这个一开始可能有点难对付,但是你会习惯的。由于这个 HE 方案四舍五入到最接近的整数,这也需要你控制神经网络的精度。

  · 矢量-矩阵乘法:这是我们的面包和黄油。结果证明,将一个秘钥转化为另一个秘钥的矩阵 M 实际上是一种线性变换。

  · 內积:在正确的上下文中,上面所提到的线性变换也可以是一个点积。

  · Sigmoid:因为我们可以做矢量-矩阵乘法,所以我们可以用足够多的乘法运算来估计任意的多项式。因为我们知道sigmod的泰勒级数多项式,所以我们可以估计一个近似的sigmoid。

  · 矩阵元素对应相乘:这个运算效率极低。我们必须得做矢量乘法或者一连串的內积。

  · 外积:可以使用内积实现外积。


做一个一般的免责声明,可能有更高效的方法来实现这些方法,但是我不想冒着破坏这个同态加密方案完整性的风险,所以我稍微后退一步地只使用了那篇论文中所提供的函数(还有sigmoid被允许的拓展)现在,我们来看看这些是怎么用Python实现的。

def sigmoid(layer_2_c):
    out_rows = list()
    for position in range(len(layer_2_c)-1):

        M_position = M_onehot[len(layer_2_c)-2][0]

        layer_2_index_c = innerProd(layer_2_c,v_onehot[len(layer_2_c)-2][position],M_position,l) / scaling_factor

        x = layer_2_index_c
        x2 = innerProd(x,x,M_position,l) / scaling_factor
        x3 = innerProd(x,x2,M_position,l) / scaling_factor
        x5 = innerProd(x3,x2,M_position,l) / scaling_factor
        x7 = innerProd(x5,x2,M_position,l) / scaling_factor

        xs = copy.deepcopy(v_onehot[5][0])
        xs[1] = x[0]
        xs[2] = x2[0]
        xs[3] = x3[0]
        xs[4] = x5[0]
        xs[5] = x7[0]

        out = mat_mul_forward(xs,H_sigmoid[0:1],scaling_factor)
        out_rows.append(out)
    return transpose(out_rows)[0]

def load_linear_transformation(syn0_text,scaling_factor = 1000):
    syn0_text *= scaling_factor
    return linearTransformClient(syn0_text.T,getSecretKey(T_keys[len(syn0_text)-1]),T_keys[len(syn0_text)-1],l)

def outer_product(x,y):
    flip = False
    if(len(x) < len(y)):
        flip = True
        tmp = x
        x = y
        y = tmp
        
    y_matrix = list()

    for i in range(len(x)-1):
        y_matrix.append(y)

    y_matrix_transpose = transpose(y_matrix)

    outer_result = list()
    for i in range(len(x)-1):
        outer_result.append(mat_mul_forward(x * onehot[len(x)-1][i],y_matrix_transpose,scaling_factor))
    
    if(flip):
        return transpose(outer_result)
    
    return outer_result

def mat_mul_forward(layer_1,syn1,scaling_factor):
    
    input_dim = len(layer_1)
    output_dim = len(syn1)

    buff = np.zeros(max(output_dim+1,input_dim+1))
    buff[0:len(layer_1)] = layer_1
    layer_1_c = buff
    
    syn1_c = list()
    for i in range(len(syn1)):
        buff = np.zeros(max(output_dim+1,input_dim+1))
        buff[0:len(syn1[i])] = syn1[i]
        syn1_c.append(buff)
    
    layer_2 = innerProd(syn1_c[0],layer_1_c,M_onehot[len(layer_1_c) - 2][0],l) / float(scaling_factor)
    for i in range(len(syn1)-1):
        layer_2 += innerProd(syn1_c[i+1],layer_1_c,M_onehot[len(layer_1_c) - 2][i+1],l) / float(scaling_factor)
    return layer_2[0:output_dim+1]

def elementwise_vector_mult(x,y,scaling_factor):
    
    y =[y]
    
    one_minus_layer_1 = transpose(y)

    outer_result = list()
    for i in range(len(x)-1):
        outer_result.append(mat_mul_forward(x * onehot[len(x)-1][i],y,scaling_factor))
        
    return transpose(outer_result)[0]

有一点我还没告诉你。为了节省时间,我预先计算了一些秘钥,矢量和矩阵,并且存储了它们。其中包括了像“全1的矢量”和独热编码(One-Hot Encoding)不同长度的矢量。这对于掩饰上面的操作还有一些我们想要能做的简单的事情来说是非常有用的。例如,sigmoid的派生是sigmoid(x) * (1 - sigmoid(x))。因此,预先计算这些变量是很方便的。预先计算的步骤如下。

# HAPPENS ON SECURE SERVER

l = 100
w = 2 ** 25

aBound = 10
tBound = 10
eBound = 10

max_dim = 10

scaling_factor = 1000

# keys
T_keys = list()
for i in range(max_dim):
    T_keys.append(np.random.rand(i+1,1))

# one way encryption transformation
M_keys = list()
for i in range(max_dim):
    M_keys.append(innerProdClient(T_keys[i],l))

M_onehot = list()
for h in range(max_dim):
    i = h+1
    buffered_eyes = list()
    for row in np.eye(i+1):
        buffer = np.ones(i+1)
        buffer[0:i+1] = row
        buffered_eyes.append((M_keys[i-1].T * buffer).T)
    M_onehot.append(buffered_eyes)
    
c_ones = list()
for i in range(max_dim):
    c_ones.append(encrypt(T_keys[i],np.ones(i+1), w, l).astype('int'))
    
v_onehot = list()
onehot = list()
for i in range(max_dim):
    eyes = list()
    eyes_txt = list()
    for eye in np.eye(i+1):
        eyes_txt.append(eye)
        eyes.append(one_way_encrypt_vector(eye,scaling_factor))
    v_onehot.append(eyes)
    onehot.append(eyes_txt)

H_sigmoid_txt = np.zeros((5,5))

H_sigmoid_txt[0][0] = 0.5
H_sigmoid_txt[0][1] = 0.25
H_sigmoid_txt[0][2] = -1/48.0
H_sigmoid_txt[0][3] = 1/480.0
H_sigmoid_txt[0][4] = -17/80640.0

H_sigmoid = list()
for row in H_sigmoid_txt:
    H_sigmoid.append(one_way_encrypt_vector(row))

如果你仔细看的话,可以发现矩阵H_sigmoid刚好是我们用多项式估计sigmoid所需的矩阵。最后,我们想用下面的方式来训练我们的神经网络。如果神经网络部分不合理的话,重新看看A Neural Network in 11 Lines of Python。我主要从那里拿来异或神经网络,然后用正确有效的函数替换掉它原本的运算符。

np.random.seed(1234)

input_dataset = [[],[0],[1],[0,1]]
output_dataset = [[0],[1],[1],[0]]

input_dim = 3
hidden_dim = 4
output_dim = 1
alpha = 0.015

# one way encrypt our training data using the public key (this can be done onsite)
y = list()
for i in range(4):
    y.append(one_way_encrypt_vector(output_dataset[i],scaling_factor))

# generate our weight values
syn0_t = (np.random.randn(input_dim,hidden_dim) * 0.2) - 0.1
syn1_t = (np.random.randn(output_dim,hidden_dim) * 0.2) - 0.1

# one-way encrypt our weight values
syn1 = list()
for row in syn1_t:
    syn1.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))

syn0 = list()
for row in syn0_t:
    syn0.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))


# begin training
for iter in range(1000):
    
    decrypted_error = 0
    encrypted_error = 0
    for row_i in range(4):

        if(row_i == 0):
            layer_1 = sigmoid(syn0[0])
        elif(row_i == 1):
            layer_1 = sigmoid((syn0[0] + syn0[1])/2.0)
        elif(row_i == 2):
            layer_1 = sigmoid((syn0[0] + syn0[2])/2.0)
        else:
            layer_1 = sigmoid((syn0[0] + syn0[1] + syn0[2])/3.0)

        layer_2 = (innerProd(syn1[0],layer_1,M_onehot[len(layer_1) - 2][0],l) / float(scaling_factor))[0:2]

        layer_2_delta = add_vectors(layer_2,-y[row_i])

        syn1_trans = transpose(syn1)

        one_minus_layer_1 = [(scaling_factor * c_ones[len(layer_1) - 2]) - layer_1]
        sigmoid_delta = elementwise_vector_mult(layer_1,one_minus_layer_1[0],scaling_factor)
        layer_1_delta_nosig = mat_mul_forward(layer_2_delta,syn1_trans,1).astype('int64')
        layer_1_delta = elementwise_vector_mult(layer_1_delta_nosig,sigmoid_delta,scaling_factor) * alpha

        syn1_delta = np.array(outer_product(layer_2_delta,layer_1)).astype('int64')

        syn1[0] -= np.array(syn1_delta[0]* alpha).astype('int64')

        syn0[0] -= (layer_1_delta).astype('int64')

        if(row_i == 1):
            syn0[1] -= (layer_1_delta).astype('int64')
        elif(row_i == 2):
            syn0[2] -= (layer_1_delta).astype('int64')
        elif(row_i == 3):
            syn0[1] -= (layer_1_delta).astype('int64')
            syn0[2] -= (layer_1_delta).astype('int64')


        # So that we can watch training, I'm going to decrypt the loss as we go.
        # If this was a secure environment, I wouldn't be doing this here. I'd send
        # the encrypted loss somewhere else to be decrypted
        encrypted_error += int(np.sum(np.abs(layer_2_delta)) / scaling_factor)
        decrypted_error += np.sum(np.abs(s_decrypt(layer_2_delta).astype('float')/scaling_factor))

    
    sys.stdout.write("\r Iter:" + str(iter) + " Encrypted Loss:" + str(encrypted_error) +  " Decrypted Loss:" + str(decrypted_error) + " Alpha:" + str(alpha))
    
    # just to make logging nice
    if(iter % 10 == 0):
        print()
    
    # stop training when encrypted error reaches a certain level
    if(encrypted_error < 25000000):
        break
        
print("\nFinal Prediction:")

for row_i in range(4):

    if(row_i == 0):
        layer_1 = sigmoid(syn0[0])
    elif(row_i == 1):
        layer_1 = sigmoid((syn0[0] + syn0[1])/2.0)
    elif(row_i == 2):
        layer_1 = sigmoid((syn0[0] + syn0[2])/2.0)
    else:
        layer_1 = sigmoid((syn0[0] + syn0[1] + syn0[2])/3.0)

    layer_2 = (innerProd(syn1[0],layer_1,M_onehot[len(layer_1) - 2][0],l) / float(scaling_factor))[0:2]
    print("True Pred:" + str(output_dataset[row_i]) + " Encrypted Prediction:" + str(layer_2) + " Decrypted Prediction:" + str(s_decrypt(layer_2) / scaling_factor))
 Iter:0 Encrypted Loss:84890656 Decrypted Loss:2.529 Alpha:0.015
 Iter:10 Encrypted Loss:69494197 Decrypted Loss:2.071 Alpha:0.015
 Iter:20 Encrypted Loss:64017850 Decrypted Loss:1.907 Alpha:0.015
 Iter:30 Encrypted Loss:62367015 Decrypted Loss:1.858 Alpha:0.015
 Iter:40 Encrypted Loss:61874493 Decrypted Loss:1.843 Alpha:0.015
 Iter:50 Encrypted Loss:61399244 Decrypted Loss:1.829 Alpha:0.015
 Iter:60 Encrypted Loss:60788581 Decrypted Loss:1.811 Alpha:0.015
 Iter:70 Encrypted Loss:60327357 Decrypted Loss:1.797 Alpha:0.015
 Iter:80 Encrypted Loss:59939426 Decrypted Loss:1.786 Alpha:0.015
 Iter:90 Encrypted Loss:59628769 Decrypted Loss:1.778 Alpha:0.015
 Iter:100 Encrypted Loss:59373621 Decrypted Loss:1.769 Alpha:0.015
 Iter:110 Encrypted Loss:59148014 Decrypted Loss:1.763 Alpha:0.015
 Iter:120 Encrypted Loss:58934571 Decrypted Loss:1.757 Alpha:0.015
 Iter:130 Encrypted Loss:58724873 Decrypted Loss:1.75 Alpha:0.0155
 Iter:140 Encrypted Loss:58516008 Decrypted Loss:1.744 Alpha:0.015
 Iter:150 Encrypted Loss:58307663 Decrypted Loss:1.739 Alpha:0.015
 Iter:160 Encrypted Loss:58102049 Decrypted Loss:1.732 Alpha:0.015
 Iter:170 Encrypted Loss:57863091 Decrypted Loss:1.725 Alpha:0.015
 Iter:180 Encrypted Loss:55470158 Decrypted Loss:1.653 Alpha:0.015
 Iter:190 Encrypted Loss:54650383 Decrypted Loss:1.629 Alpha:0.015
 Iter:200 Encrypted Loss:53838756 Decrypted Loss:1.605 Alpha:0.015
 Iter:210 Encrypted Loss:51684722 Decrypted Loss:1.541 Alpha:0.015
 Iter:220 Encrypted Loss:54408709 Decrypted Loss:1.621 Alpha:0.015
 Iter:230 Encrypted Loss:54946198 Decrypted Loss:1.638 Alpha:0.015
 Iter:240 Encrypted Loss:54668472 Decrypted Loss:1.63 Alpha:0.0155
 Iter:250 Encrypted Loss:55444008 Decrypted Loss:1.653 Alpha:0.015
 Iter:260 Encrypted Loss:54094286 Decrypted Loss:1.612 Alpha:0.015
 Iter:270 Encrypted Loss:51251831 Decrypted Loss:1.528 Alpha:0.015
 Iter:276 Encrypted Loss:24543890 Decrypted Loss:0.732 Alpha:0.015
 Final Prediction:
True Pred:[0] Encrypted Prediction:[-3761423723.0718255 0.0] Decrypted Prediction:[-0.112]
True Pred:[1] Encrypted Prediction:[24204806753.166267 0.0] Decrypted Prediction:[ 0.721]
True Pred:[1] Encrypted Prediction:[23090462896.17028 0.0] Decrypted Prediction:[ 0.688]
True Pred:[0] Encrypted Prediction:[1748380342.4553354 0.0] Decrypted Prediction:[ 0.052]

当训练这个神经网络的时候,这是我看到的输出。调整有点难办,因为加密的干扰信号和低精度某些结合产生了少量的chunky learning。训练过程也相当的慢。大量的转置操作的代价是多么的昂贵。我十分确信我可以做一些更简单的事,然而,我想从安全的角度证明这个概念。


关键点:

  · 网络的加权全部加密

  · 数据是加密的...全是0和1

  · 训练完成后,神经网络可以被解密以便后续的操作或训练(或者换一个不同加密秘钥)。

  · 训练中丢掉的和输出的预测也都是被加密的值。为了能解释说明该神经网络,我们必须得解码它们。


Part 9:情绪分类


为了让这件事更真实一些,在IMDB情绪回顾上有一个相同的基于network from Udacity's Deep Learning Nanodegree的神经网络训练。你可以点击此处查看所有源码。


import time
import sys
import numpy as np

# Let's tweak our network from before to model these phenomena
class SentimentNetwork:
    def __init__(self, reviews,labels,min_count = 10,polarity_cutoff = 0.1,hidden_nodes = 8, learning_rate = 0.1):
       
        np.random.seed(1234)
    
        self.pre_process_data(reviews, polarity_cutoff, min_count)
        
        self.init_network(len(self.review_vocab),hidden_nodes, 1, learning_rate)
        
        
    def pre_process_data(self,reviews, polarity_cutoff,min_count):
        
        print("Pre-processing data...")
        
        positive_counts = Counter()
        negative_counts = Counter()
        total_counts = Counter()

        for i in range(len(reviews)):
            if(labels[i] == 'POSITIVE'):
                for word in reviews[i].split(" "):
                    positive_counts[word] += 1
                    total_counts[word] += 1
            else:
                for word in reviews[i].split(" "):
                    negative_counts[word] += 1
                    total_counts[word] += 1

        pos_neg_ratios = Counter()

        for term,cnt in list(total_counts.most_common()):
            if(cnt >= 50):
                pos_neg_ratio = positive_counts[term] / float(negative_counts[term]+1)
                pos_neg_ratios[term] = pos_neg_ratio

        for word,ratio in pos_neg_ratios.most_common():
            if(ratio > 1):
                pos_neg_ratios[word] = np.log(ratio)
            else:
                pos_neg_ratios[word] = -np.log((1 / (ratio + 0.01)))
        
        review_vocab = set()
        for review in reviews:
            for word in review.split(" "):
                if(total_counts[word] > min_count):
                    if(word in pos_neg_ratios.keys()):
                        if((pos_neg_ratios[word] >= polarity_cutoff) or (pos_neg_ratios[word] = (total_pred / float(i+2)) and label == 'POSITIVE'):
                correct_so_far += 1
            if((s_decrypt(layer_2)[0] / scaling_factor) < (total_pred / float(i+2)) and label == 'NEGATIVE'):
                correct_so_far += 1

            reviews_per_second = i / float(time.time() - start)

            sys.stdout.write("\rProgress:" + str(100 * i/float(len(training_reviews_raw)))[:4] + "% Speed(reviews/sec):" + str(reviews_per_second)[0:5] + " #Correct:" + str(correct_so_far) + " #Trained:" + str(i+1) + " Training Accuracy:" + str(correct_so_far * 100 / float(i+1))[:4] + "%")
            if(i % 100 == 0):
                print(i)

    
    def test(self, testing_reviews, testing_labels):
        
        correct = 0
        
        start = time.time()
        
        for i in range(len(testing_reviews)):
            pred = self.run(testing_reviews[i])
            if(pred == testing_labels[i]):
                correct += 1
            
            reviews_per_second = i / float(time.time() - start)
            
            sys.stdout.write("\rProgress:" + str(100 * i/float(len(testing_reviews)))[:4] \
                             + "% Speed(reviews/sec):" + str(reviews_per_second)[0:5] \
                            + "% #Correct:" + str(correct) + " #Tested:" + str(i+1) + " Testing Accuracy:" + str(correct * 100 / float(i+1))[:4] + "%")
    
    def run(self, review):
        
        # Input Layer


        # Hidden layer
        self.layer_1 *= 0
        unique_indices = set()
        for word in review.lower().split(" "):
            if word in self.word2index.keys():
                unique_indices.add(self.word2index[word])
        for index in unique_indices:
            self.layer_1 += self.weights_0_1[index]
        
        # Output layer
        layer_2 = self.sigmoid(self.layer_1.dot(self.weights_1_2))
        
        if(layer_2[0] >= 0.5):
            return "POSITIVE"
        else:
            return "NEGATIVE"


Progress:0.0% Speed(reviews/sec):0.0 #Correct:1 #Trained:1 Training Accuracy:100.%0
Progress:0.41% Speed(reviews/sec):1.978 #Correct:66 #Trained:101 Training Accuracy:65.3%100
Progress:0.83% Speed(reviews/sec):2.014 #Correct:131 #Trained:201 Training Accuracy:65.1%200
Progress:1.25% Speed(reviews/sec):2.011 #Correct:203 #Trained:301 Training Accuracy:67.4%300
Progress:1.66% Speed(reviews/sec):2.003 #Correct:276 #Trained:401 Training Accuracy:68.8%400
Progress:2.08% Speed(reviews/sec):2.007 #Correct:348 #Trained:501 Training Accuracy:69.4%500
Progress:2.5% Speed(reviews/sec):2.015 #Correct:420 #Trained:601 Training Accuracy:69.8%600
Progress:2.91% Speed(reviews/sec):1.974 #Correct:497 #Trained:701 Training Accuracy:70.8%700
Progress:3.33% Speed(reviews/sec):1.973 #Correct:581 #Trained:801 Training Accuracy:72.5%800
Progress:3.75% Speed(reviews/sec):1.976 #Correct:666 #Trained:901 Training Accuracy:73.9%900
Progress:4.16% Speed(reviews/sec):1.983 #Correct:751 #Trained:1001 Training Accuracy:75.0%1000
Progress:4.33% Speed(reviews/sec):1.940 #Correct:788 #Trained:1042 Training Accuracy:75.6%
....



Part 10:相对于数据加密的优势


和这个方法最相似的方法是加密训练中的数据,然后使用加密的数据训练神经网络(接收加密的输入和预测加密的输出)。这是一个非常棒的想法。然而,它确实有一些缺点。首先,加密这些数据意味着这个神经网络对于没有加密数据的私钥的人来说毫无价值。这导致来自不同私有来源的数据不可能在同一个深度学习模型上被训练。但是绝大多数的商业应用都有这个需求,需要消费者数据的集合。理论上,我们想让每个消费者通过他们的私人秘钥受到保护,但是同态加密这些数据要求所有人都使用相同的秘钥。


但是,加密神经网络没有这种限制。


利用上面提到的方法,你可以训练一个常规的,未加密的神经网络,加密它,并利用一个公钥将它发送到Party A(Party A 使用他们自己的数据训练它...但这些数据仍然属于他们)。然后,你可以拿回这个神经网络,解密它,用另一个不同的秘钥重新加密它并把它送给Party B,Party B使用他们的数据对它做一些训练。由于神经网络本身是被加密的部分,所以你拥有对一路上你正在捕获的智能的完全控制权。Party A 和Party B 无从得知他们收到的是同一个神经网络,这一切都可以发生而无需他们之前看过或利用他们自己的数据使用过该神经网络。你,这个公司,继续持有对该神经网络中的知识产权的控制权,而每个使用者继续持有对他们自己数据的控制权。


Part 11:未来的工作


一定有更快和更安全的同态加密算法。我认为,将这项工作移交到YASHE是向正确方向上迈出的一步。或许有一种框架既可以使得加密对用户来说更加简单,同时它只有很少的系统并发症。大体来讲,要使这些想法中的许多达到产品质量水平,HE 需要变得更快。但是,进步总是来的异常迅速。我相信在不久的将来我们就会找到它们。


Part 12:潜在应用


分散化AI:公司可以把将要训练或使用的模型部署在他们的智能不存在被剽窃风险的地方。


保护消费者隐私:先前的应用开启了一种可能性,那就是消费者可以很容易地掌控他们的数据,并且选择不同的基于他们生活的正在被训练的模型,而不是将他们的数据送到别的什么地方。如果借助分散化使他们的知识产权不处于风险之中,公司就没有什么借口了。数据就是权利,它应该回到消费者手里。


控制超级智能:神经网络可以变得越来越聪明,但是除非它掌控了密钥,否则它自己能做的只会是预测一些乱七八糟的东西。




原文链接:http://iamtrask.github.io/2017/03/17/safe-ai/

本文由 看雪翻译小组 hesir 编译


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (4)
雪    币: 689
活跃值: (427)
能力值: ( LV11,RANK:190 )
在线值:
发帖
回帖
粉丝
zplusplus 1 2017-4-17 08:26
2
0
内容有问题(重复粘贴了),检查一下啊。看了好几遍,确定不是我眼花了
雪    币: 364
活跃值: (56)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
missdiog 2017-4-17 10:55
3
0

发了4次 

雪    币: 870
活跃值: (2264)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
s1ber 2018-9-21 11:57
4
0
支持一下
雪    币: 965
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ielts 2018-10-17 16:50
5
0
支持一下
游客
登录 | 注册 方可回帖
返回