在分布式AI環(huán)境下,同態(tài)加密神經(jīng)網(wǎng)絡(luò)有助于保護(hù)商業(yè)公司知識(shí)產(chǎn)權(quán)和消費(fèi)者隱私。讓我們和DeepMind數(shù)據(jù)科學(xué)家、Udacity深度學(xué)習(xí)導(dǎo)師Andrew Trask一起,來(lái)看看如何基于Numpy實(shí)現(xiàn)同態(tài)加密神經(jīng)網(wǎng)絡(luò)吧。
TLDR:在這篇文章中,我們將訓(xùn)練一個(gè)在訓(xùn)練階段完全加密的神經(jīng)網(wǎng)絡(luò)(在未加密的數(shù)據(jù)上訓(xùn)練)。得到的神經(jīng)網(wǎng)絡(luò)將具備兩個(gè)有益的性質(zhì)。首先,保護(hù)神經(jīng)網(wǎng)絡(luò)的智能免遭竊取,使有價(jià)值的AI可以在不安全的環(huán)境中加以訓(xùn)練而不用冒智能遭竊的風(fēng)險(xiǎn)。其次,網(wǎng)絡(luò)只能進(jìn)行加密預(yù)測(cè)(大概對(duì)外部世界毫無(wú)影響,因?yàn)樵跊](méi)有密鑰的情況下,外部世界無(wú)法理解預(yù)測(cè))。這在用戶和超智能間構(gòu)成了一個(gè)有價(jià)值的權(quán)力失衡。如果AI是同態(tài)加密的,那么在AI看來(lái),整個(gè)外部世界也是同態(tài)加密的。一個(gè)控制密鑰的人類可以選擇解鎖AI本身(將AI釋放到世界中)或僅僅解密AI做出的單個(gè)預(yù)測(cè)(看起來(lái)更安全)。
超智能
很多人都擔(dān)憂超智能有一天會(huì)選擇傷害人類。史蒂芬·霍金曾呼吁建立新的世界政府來(lái)管理我們賦予人工智能的能力,以防人工智能最終摧毀人類。這些是相當(dāng)大膽的主張,我認(rèn)為它們反映了科學(xué)界和整個(gè)世界對(duì)這一問(wèn)題的普遍擔(dān)憂。本文將是一篇介紹解決這一問(wèn)題的潛在技術(shù)方案的教程,我將通過(guò)一些玩具樣例代碼來(lái)演示這一方法。
目標(biāo)很簡(jiǎn)單。我們想要?jiǎng)?chuàng)建未來(lái)會(huì)變得非常智能的AI技術(shù)(智能到可以解決治愈癌癥、終結(jié)世界上的饑餓等問(wèn)題),但是這樣的智能受人類的控制(基于密鑰),因而其智能的應(yīng)用是受限的。不受限的學(xué)習(xí)是很棒的,但知識(shí)的不受限的應(yīng)用可能具有潛在危險(xiǎn)性。
為了介紹這一想法,讓我先簡(jiǎn)要介紹兩個(gè)非常激動(dòng)人心的研究領(lǐng)域:深度學(xué)習(xí)和同態(tài)加密。
一、什么是深度學(xué)習(xí)?
深度學(xué)習(xí)是用于自動(dòng)化智能的工具套件,主要基于神經(jīng)網(wǎng)絡(luò)。這一計(jì)算機(jī)科學(xué)的領(lǐng)域,是最近AI技術(shù)爆發(fā)的主要?jiǎng)恿?,因?yàn)樯疃葘W(xué)習(xí)在許多智能任務(wù)上超越了先前的表現(xiàn)記錄。例如,他是DeepMind的AlphaGo系統(tǒng)的主要組成部分。
神經(jīng)網(wǎng)絡(luò)基于輸入做出預(yù)測(cè)。它通過(guò)試錯(cuò)法學(xué)習(xí)做出有效的預(yù)測(cè)。剛開始,它做出一個(gè)預(yù)測(cè)(起初基本上是隨機(jī)預(yù)測(cè)),接著接收一個(gè)“錯(cuò)誤信號(hào)”,該信號(hào)表明它的預(yù)測(cè)過(guò)高或過(guò)低(通常是概率)。在這一周期重復(fù)數(shù)百萬(wàn)次后,網(wǎng)絡(luò)開始搞明白情況。想要了解更多神經(jīng)網(wǎng)絡(luò)如何工作的細(xì)節(jié),請(qǐng)參考基于Numpy實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò):反向傳播一文。
這里最神奇的是錯(cuò)誤信號(hào)。如果不告知預(yù)測(cè)的表現(xiàn)有多好,神經(jīng)網(wǎng)絡(luò)無(wú)法學(xué)習(xí)。牢記這一點(diǎn)。
二、什么是同態(tài)加密?
顧名思義,同態(tài)加密是一種加密的形式。在不對(duì)稱情形下,它可以接受完全可讀的文本,然后基于“公鑰”將其轉(zhuǎn)變?yōu)閬y碼。更重要的是,它可以基于“私鑰”將亂碼轉(zhuǎn)回同樣的文本。然而,除非你有“私鑰”,(理論上)你無(wú)法解碼加密后的亂碼。
同態(tài)加密是一種特殊形式的加密。它允許某人在無(wú)法閱讀信息的前提下以特定的方式修改加密信息。例如,同態(tài)加密可以應(yīng)用于數(shù)字上,讓加密過(guò)的數(shù)字可以進(jìn)行乘法和加法運(yùn)算而無(wú)需解密數(shù)字。下面是一些玩具樣例。
現(xiàn)在出現(xiàn)了越來(lái)越多的同態(tài)加密方案,各有不同的性質(zhì)。這是一個(gè)相對(duì)年輕的領(lǐng)域,仍有一些明顯的問(wèn)題有待解決,不過(guò)我們將這些內(nèi)容留待以后討論。
就目前而言,讓我們從整數(shù)公鑰加密方案開始。整數(shù)公鑰加密方案是一種乘法和加法上的同態(tài)加密,允許進(jìn)行上圖的操作。不僅如此,由于公鑰允許“單向”加密,你甚至可以進(jìn)行未加密數(shù)字和加密數(shù)字間的運(yùn)算(通過(guò)單向加密),上圖的2 * Cypher A就是一個(gè)例子。(某些加密方案甚至不要求這一點(diǎn)……不過(guò)同樣……我們以后討論這個(gè)。)
三、我們可以結(jié)合這兩者嗎?
也許深度學(xué)習(xí)和同態(tài)加密之間最頻繁的互動(dòng)體現(xiàn)在數(shù)據(jù)隱私上。當(dāng)你同態(tài)加密數(shù)據(jù)時(shí),你無(wú)法讀取數(shù)據(jù)但仍然可以保持大多數(shù)有趣的統(tǒng)計(jì)學(xué)結(jié)構(gòu)。這讓人們得以在加密數(shù)據(jù)上訓(xùn)練模型(CryptoNets)。甚至有一家名為Numer.ai的初創(chuàng)對(duì)沖基金加密昂貴的專有數(shù)據(jù),允許任何人嘗試訓(xùn)練機(jī)器學(xué)習(xí)模型預(yù)測(cè)股票市場(chǎng)。通常這不可能辦到,因?yàn)闀?huì)導(dǎo)致放棄極為昂貴的信息(不可能基于通常的加密數(shù)據(jù)訓(xùn)練模型)。
然而,本文將反其道而行,加密神經(jīng)網(wǎng)絡(luò),然后在解密信息上加以訓(xùn)練。
復(fù)雜度驚人的神經(jīng)網(wǎng)絡(luò),事實(shí)上可以劃分成很少(少得驚人)幾種組件,這些組件不斷重復(fù)以構(gòu)成神經(jīng)網(wǎng)絡(luò)。其實(shí),僅僅基于如下操作,就可以創(chuàng)建很多最先進(jìn)的神經(jīng)網(wǎng)絡(luò):
加法
乘法
除法
減法
Sigmoid
Tanh
指數(shù)函數(shù)
那么,讓我們提出這一明顯的技術(shù)問(wèn)題,我們能否同態(tài)加密神經(jīng)網(wǎng)絡(luò)本身?我們會(huì)想這么做嗎?結(jié)果發(fā)現(xiàn),基于一些保守的逼近,這是可以辦到的。
加法 —— 開箱即用
乘法 —— 開箱即用
除法 —— 開箱即用?只是乘法的倒數(shù)
加法 —— 開箱即用?只是加上負(fù)數(shù)
Sigmoid —— 嗯……也許有點(diǎn)難度
Tanh —— 嗯……也許有點(diǎn)難度
指數(shù)函數(shù) —— 嗯……也許有點(diǎn)難度
看起來(lái)實(shí)現(xiàn)除法和減法會(huì)是相當(dāng)微不足道的事情,但那些更復(fù)雜的函數(shù)就……好吧……比簡(jiǎn)單的加法和乘法更復(fù)雜。為了嘗試同態(tài)加密一個(gè)深度神經(jīng)網(wǎng)絡(luò),我們還需要一個(gè)秘密原料。
四、泰勒級(jí)數(shù)展開
也許你在小學(xué)學(xué)過(guò),泰勒級(jí)數(shù)允許我們使用無(wú)限項(xiàng)加法、減法、乘法、除法來(lái)計(jì)算一個(gè)復(fù)雜(非線性)函數(shù)。這很完美!(除了無(wú)限部分)。幸運(yùn)的是,如果你早早地停止了計(jì)算精確的泰勒級(jí)數(shù)展開,你仍然能得到手頭的函數(shù)的一個(gè)逼近值。下面是通過(guò)泰勒級(jí)數(shù)逼近一些流行函數(shù)的例子(來(lái)源)。
等下!這里有指數(shù)!別擔(dān)心。指數(shù)不過(guò)是反復(fù)相乘。下面是使用泰勒級(jí)數(shù)逼近sigmoid函數(shù)的python實(shí)現(xiàn)(相關(guān)公式見Wolfram Alpha)。我們將選取級(jí)數(shù)的開始幾項(xiàng),看看能逼近到什么程度。
import numpy as np
def sigmoid_exact(x):
return1 / (1 + np.exp(-x))
# 使用泰勒級(jí)數(shù)
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("\n輸入:" + str(lil_number))
print("精確的Sigmoid值:" + str(sigmoid_exact(lil_number)))
print("逼近Sigmoid:" + str(sigmoid_approximation(lil_number)))
結(jié)果:
輸入:0.1
精確的Sigmoid值:0.52497918747894
逼近Sigmoid:0.5249791874999999
輸入:0.2
精確的Sigmoid值:0.549833997312478
逼近Sigmoid:0.549834
輸入:0.3
精確的Sigmoid值:0.574442516811659
逼近Sigmoid:0.5744425624999999
輸入:0.4
精確的Sigmoid值:0.598687660112452
逼近Sigmoid:0.598688
輸入:0.5
精確的Sigmoid值:0.6224593312018546
逼近Sigmoid:0.6224609375000001
輸入:0.6
精確的Sigmoid值:0.6456563062257954
逼近Sigmoid:0.6456620000000001
輸入:0.7
精確的Sigmoid值:0.6681877721681662
逼近Sigmoid:0.6682043125000001
輸入:0.8
精確的Sigmoid值:0.6899744811276125
逼近Sigmoid:0.690016
輸入:0.9
精確的Sigmoid值:0.7109495026250039
逼近Sigmoid:0.7110426875
輸入:1.0
精確的Sigmoid值:0.7310585786300049
逼近Sigmoid:0.73125
僅僅使用了泰勒級(jí)數(shù)的前4項(xiàng),我們已經(jīng)相當(dāng)逼近sigmoid函數(shù)了。既然我們已經(jīng)具備了通用的策略,是時(shí)候選擇一個(gè)同態(tài)加密算法了。
五、選擇加密算法
同態(tài)加密是一個(gè)相對(duì)較新的領(lǐng)域,其中的主要里程碑是Craig Gentry在2009年發(fā)現(xiàn)的第一個(gè)全同態(tài)加密算法。這一里程碑為許多后來(lái)者建立了據(jù)點(diǎn)。大部分關(guān)于同態(tài)加密的激動(dòng)人心的研究圍繞開發(fā)圖靈完備的同態(tài)加密計(jì)算機(jī)展開。因此,對(duì)全同態(tài)加密方案的需求讓人們?cè)噲D找到一個(gè)算法,使得進(jìn)行任意計(jì)算所需的多種邏輯門都可以通這一算法高效而安全地計(jì)算。大體的希望是人們能夠安全地將工作放到云端,而不必冒發(fā)送到云端的數(shù)據(jù)被發(fā)送者以外的人讀取的風(fēng)險(xiǎn)。這是一個(gè)非常酷的想法,也取得了很多進(jìn)展。
然而,這一角度存在一些缺陷。一般而言,相比普通電腦,大多數(shù)全同態(tài)加密方案慢得讓人懷疑人生(目前還不實(shí)用)。這鼓舞了一系列有趣的研究,將操作種類限制為某種程度上同態(tài),這樣至少可以進(jìn)行某些操作。不那么靈活,但是更快,這是常見的計(jì)算上的折衷。
這是我們想要開始查看的地方。理論上,我們想要一個(gè)操作浮點(diǎn)數(shù)的同態(tài)加密方案(不過(guò)很快我們將看到,最終我們選擇了操作整數(shù)的方案),而不是操作二進(jìn)制值的方案。二進(jìn)制可以工作,但它不僅要求全同態(tài)加密的靈活性(以性能為代價(jià)),還要求我們管理二進(jìn)制表示和我們想要計(jì)算的數(shù)學(xué)運(yùn)算之間的邏輯。一個(gè)不那么強(qiáng)大,為浮點(diǎn)運(yùn)算定制的HE(HE為同態(tài)加密Homomorphic Encryption的縮寫)算法會(huì)更合適。
盡管加上了這一限制,仍有非常多的選擇。下面是一些具備我們需要的特性的流行算法:
Efficient Homomorphic Encryption on Integer Vectors and Its Applications(基于整數(shù)向量的高效同態(tài)加密及其應(yīng)用)
Yet Another Somewhat Homomorphic Encryption (YASHE)(又一個(gè)某種程度上的同態(tài)加密)
Somewhat Practical Fully Homomorphic Encryption (FV)(某種程度上實(shí)用的全同態(tài)加密)
Fully Homomorphic Encryption without Bootstrapping(非自舉的全同態(tài)加密)
最佳的選擇可能是YASHE或FV。YASHE是流行的CryptoNet使用的算法,對(duì)浮點(diǎn)運(yùn)算的支持很棒。然而,它相當(dāng)復(fù)雜。為了讓這篇文章容易閱讀、便于嘗試,我們將選擇稍微不那么高級(jí)(可能也不那么安全)的Efficient Integer Vector Homomorphic Encryption(高效整數(shù)向量同態(tài)加密)。然而,我認(rèn)為非常值得指出的是,在你閱讀本文的時(shí)候,更多新的HE算法正在開發(fā)之中,同時(shí)本文展示的想法通用于任何在整數(shù)或浮點(diǎn)數(shù)的加法和乘法上同態(tài)加密的方案。甚至說(shuō),我的愿望是引起對(duì)HE的這一應(yīng)用的關(guān)注,以便更多的為深度學(xué)習(xí)優(yōu)化的HE算法能被開發(fā)出來(lái)。
Yu、Lai、Paylor的論文Efficient Integer Vector Homomorphic Encryption詳細(xì)描述了這一算法,相應(yīng)的實(shí)現(xiàn)可以在GitHub上獲?。╦amespayor/vector-homomorphic-encryption)。主要部分在C++文件vhe.cpp中。下面我們引導(dǎo)讀者閱讀代碼的一個(gè)python移植,說(shuō)明代碼是干什么的。如果你選擇實(shí)現(xiàn)一個(gè)更高級(jí)的方案,這也會(huì)很有幫助,因?yàn)橛幸恍┲黝}相對(duì)而言是通用的(一般函數(shù)名,變量名,等等)。
六、Python中的同態(tài)加密
首先是一些同態(tài)加密術(shù)語(yǔ):
明文(plaintext)未加密數(shù)據(jù)。也叫“消息”。在我們的例子中,這將是一些表示神經(jīng)網(wǎng)絡(luò)的數(shù)字。
密文(cyphertext)加密數(shù)據(jù)。我們將在密文之上進(jìn)行數(shù)學(xué)運(yùn)算,這些運(yùn)算會(huì)改變底層的明文。
公鑰(public key)偽隨機(jī)數(shù)字序列,讓任何人得以加密數(shù)據(jù)。可以和別人分享,因?yàn)椋ɡ碚撋希┕€只能用于加密。
私鑰/密鑰(private/secret key)偽隨機(jī)數(shù)字序列,讓你解密被公鑰加密的數(shù)據(jù)。你不想和別人分享私鑰。否則,別人可以解密你的消息。
對(duì)應(yīng)的變量名(不同的同態(tài)加密技術(shù)都傾向于使用這些標(biāo)準(zhǔn)變量名):
S表示密鑰/私鑰的矩陣。用于解密。
M公鑰。用于加密和進(jìn)行數(shù)學(xué)運(yùn)算。在有些算法中,不是所有數(shù)學(xué)運(yùn)算都需要公鑰。但這一算法非常廣泛地使用公鑰。
c加密數(shù)據(jù)向量,密文。
x消息,即明文。有些論文使用m作明文的變量名。
w單個(gè)“加權(quán)(weighting)”標(biāo)量變量,用于重加權(quán)輸入消息x(讓它一致地更長(zhǎng)或更短)。這一變量用于調(diào)節(jié)信噪比。加強(qiáng)信號(hào)后,對(duì)于給定的操作而言,消息較不容易受噪聲影響。然而,過(guò)于加強(qiáng)信號(hào),會(huì)增加完全毀壞數(shù)據(jù)的概率。這是一個(gè)平衡。
E或e一般指隨機(jī)噪聲。在某些情形下,指用公鑰加密數(shù)據(jù)前添加的噪聲。一般而言,噪聲使解密更困難。噪聲使同一消息的兩次加密可以不一樣,在讓消息難以破解方面,這很重要。注意,取決于算法和實(shí)現(xiàn),這可能是一個(gè)向量,也可能是一個(gè)矩陣。在其他情形下,指隨操作積累的噪聲,詳見后文。
和許多數(shù)學(xué)論文的慣用法一樣,大寫字母對(duì)應(yīng)矩陣,小寫字母對(duì)應(yīng)向量,斜體小寫對(duì)應(yīng)標(biāo)量。我們關(guān)注同態(tài)加密的四種操作:公私鑰對(duì)生成,單向加密,解密,數(shù)學(xué)運(yùn)算。讓我們從解密開始。
左邊的公式描述了密鑰S和消息x的一般關(guān)系。右邊的公式顯示了如何使用密鑰解密消息。不知道你注意到?jīng)]有,右邊的公式并不包含e?;旧?,同態(tài)加密技術(shù)一般引入足夠多的噪聲使沒(méi)有密鑰的情況下難以破解出原始消息,但是引入的噪聲的量又足夠少,當(dāng)你確實(shí)具有密鑰時(shí)噪聲可以通過(guò)取整忽略。右邊的公式中的框表示“取整到最接近的整數(shù)”。其他同態(tài)加密算法使用不同的取整。模數(shù)運(yùn)算幾乎普遍存在。而加密則生成使上述關(guān)系為真的c. 如果S是一個(gè)隨機(jī)矩陣,那么c很難解密。一個(gè)簡(jiǎn)單的、非對(duì)稱的生成加密鑰的方式是找到密鑰的逆矩陣。讓我們看下相應(yīng)的Python代碼。
import numpy as np
def generate_key(w,m,n):
S = (np.random.rand(m,n) * w / (2 ** 16)) # 可證明 max(S) < w
return S
def encrypt(x,S,m,n,w):
assert len(x) == len(S)
e = (np.random.rand(m)) # 可證明 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')
x = np.array([0,1,2,5])
m = len(x)
n = m
w = 16
S = generate_key(w,m,n)
你可以在Jupyter Notebook中試著運(yùn)行上面的代碼,進(jìn)行一些操作:
注意,我們可以對(duì)密文進(jìn)行一些基本的運(yùn)算,這些運(yùn)算改動(dòng)了相應(yīng)的明文。很優(yōu)雅,不是嗎?
七、優(yōu)化加密
重要一課:回顧一下之前的公式。如果密鑰S是一個(gè)單位矩陣,那么c不過(guò)是輸入x的一個(gè)重加權(quán)的、略帶噪聲的版本。如果你不明白上面的話,請(qǐng)Google“單位矩陣教程”。限于篇幅,這里就不詳細(xì)介紹單位矩陣了。
這引導(dǎo)我們思考加密是如何進(jìn)行的。論文作者沒(méi)有顯式地分配一對(duì)獨(dú)立的“公鑰”和“私鑰”,相反,提出了一種“鑰交換”技術(shù),將私鑰S替換為S'。更具體地,這一私鑰交換技術(shù)涉及生成一個(gè)可以進(jìn)行該變換的矩陣M。由于M具備將消息從未加密狀態(tài)(單位矩陣密鑰)轉(zhuǎn)換為加密狀態(tài)(隨機(jī)而難以猜測(cè)的密鑰),這個(gè)M矩陣正好可以用作我們的公鑰!
上面一段話包含許多信息,我們也許講得太快了。讓我們重新概括一下。
發(fā)生了什么……
基于上面兩個(gè)公式,如果密鑰是一個(gè)單位矩陣,那么消息是未加密的。
基于上面兩個(gè)公式,如果密鑰是一個(gè)隨機(jī)矩陣,那么消息是加密的。
我們構(gòu)造一個(gè)矩陣M將一個(gè)密鑰轉(zhuǎn)換為另一個(gè)私鑰。
當(dāng)矩陣M將單位矩陣轉(zhuǎn)換為一個(gè)隨機(jī)密鑰時(shí),根據(jù)定義,它使用單向加密方式加密了消息。
由于M充當(dāng)了“單向加密”的角色,我們稱它為“公鑰”,并且可以像公鑰一樣分發(fā)它,因?yàn)樗鼰o(wú)法用于解密。
好了,不多拖延了,讓我們看下這一切是如何通過(guò)Python實(shí)現(xiàn)的。
import numpy as np
def generate_key(w,m,n):
S = (np.random.rand(m,n) * w / (2 ** 16)) # 可證明 max(S) < w
return S
def encrypt(x,S,m,n,w):
assert len(x) == len(S)
e = (np.random.rand(m)) # 可證明 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大體上是單位矩陣,然后在其之上連接一個(gè)隨機(jī)向量T。因此T具備所有密鑰所需的信息,不過(guò)我們?nèi)匀恍枰獦?gòu)建一個(gè)尺寸為S的矩陣使得一切可以工作。
八、創(chuàng)建一個(gè)XOR神經(jīng)網(wǎng)絡(luò)
既然我們已經(jīng)知道如何加密和解密消息(以及進(jìn)行基本的加法和乘法計(jì)算),是時(shí)候嘗試擴(kuò)展剩余的運(yùn)算,以便構(gòu)建一個(gè)簡(jiǎn)單的XOR神經(jīng)網(wǎng)絡(luò)。盡管從技術(shù)上說(shuō),神經(jīng)網(wǎng)絡(luò)不過(guò)是一系列非常簡(jiǎn)單的操作,我們還是需要一些操作的組合以實(shí)現(xiàn)便利的功能。下面我將描述我們需要的每項(xiàng)操作,以及在一個(gè)較高的抽象層次上,我們是如何實(shí)現(xiàn)這些操作的(基本上是我們將使用的加法和乘法的序列)。接著我會(huì)向你展示代碼。關(guān)于一些細(xì)節(jié),請(qǐng)參考前面提到的論文。
浮點(diǎn)數(shù)我們將簡(jiǎn)單地scale浮點(diǎn)數(shù)到整數(shù)。我們將在整數(shù)上訓(xùn)練我們的網(wǎng)絡(luò)(把整數(shù)當(dāng)成浮點(diǎn)數(shù))。比如,假設(shè)scale=1000,0.2 * 0.5 = 0.1就是200 * 500 = 100000。還原時(shí),100000 / (1000 * 1000) = 0.1(因?yàn)槲覀兪褂昧顺朔ǎ孕枰?000的平方)。初看起來(lái)這很有技巧性,但你會(huì)適應(yīng)的。由于我們使用的HE方案取整到最接近的整數(shù),這也讓我們得以控制神經(jīng)網(wǎng)絡(luò)的精度。
向量矩陣乘法這是我們的黃油面包(最基本的操作)。事實(shí)上,轉(zhuǎn)換密鑰的矩陣M是一種線性變換的方式。
內(nèi)積在合適的背景下,上述線性變換可能是內(nèi)積。
sigmoid由于我們可以進(jìn)行向量矩陣乘法運(yùn)算,基于足夠的乘法,我們可以演算任意多項(xiàng)式的值。因?yàn)槲覀円呀?jīng)知道了對(duì)應(yīng)sigmoid的泰勒級(jí)數(shù)多項(xiàng)式,我們可以演算sigmoid的逼近值!
逐元素矩陣乘法這一操作驚人地低效。我們需要進(jìn)行向量矩陣乘法或一系列內(nèi)積運(yùn)算。
外積我們可以通過(guò)掩碼和內(nèi)積完成這一運(yùn)算。
聲明一下,可能存在完成這些運(yùn)算的更高效的方法,但我不想冒打破同態(tài)加密方案完整性的風(fēng)險(xiǎn)。所以某種程度上我是通過(guò)論文中提供的函數(shù)來(lái)反推如何完成上述運(yùn)算的(除了算法容許的sigmoid擴(kuò)展)?,F(xiàn)在,讓我們看看完成這些的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]
有一點(diǎn)我之前沒(méi)有告訴你。為了節(jié)省時(shí)間,我預(yù)計(jì)算了一些鑰、向量、矩陣,并對(duì)它們作了排序。這包括完全由1組成的向量,不同長(zhǎng)度的one-hot編碼向量。這有助于上面的掩碼操作,以及其他我們希望可以做到的簡(jiǎn)單操作。例如,sigmoid的導(dǎo)數(shù)是sigmoid(x) * (1 - sigmoid(x))。因此,預(yù)計(jì)算這些變量會(huì)很方便。下面是預(yù)計(jì)算步驟。
# 在安全的服務(wù)端進(jìn)行
l = 100
w = 2 ** 25
aBound = 10
tBound = 10
eBound = 10
max_dim = 10
scaling_factor = 1000
# 鑰
T_keys = list()
for i in range(max_dim):
T_keys.append(np.random.rand(i+1,1))
# 單向加密變換
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))
如果你仔細(xì)查看了上面的代碼,你會(huì)注意到H_sigmoid矩陣是我們需要的用于演算sigmoid多項(xiàng)式的矩陣。最后,我們使用如下代碼訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)。如果不明白神經(jīng)網(wǎng)絡(luò)的部分,你可以溫習(xí)下基于Numpy實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò):反向傳播一文。我基本上使用了文中的XOR網(wǎng)絡(luò),使用適當(dāng)?shù)墓ぞ吆瘮?shù)替換了其中一些操作,以加密權(quán)重。
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
# 使用公鑰單向加密訓(xùn)練數(shù)據(jù)(可就地進(jìn)行)
y = list()
for i in range(4):
y.append(one_way_encrypt_vector(output_dataset[i],scaling_factor))
# 生成權(quán)重
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
# 單向加密權(quán)重
syn1 = list()
for row insyn1_t:
syn1.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))
syn0 = list()
for row insyn0_t:
syn0.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))
# 開始訓(xùn)練
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')
# 如果有安全性要求,可以將加密的損失發(fā)送到別處解密。
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 迭代" + str(iter) + " 加密損失:" + str(encrypted_error) + " 解密損失:" + str(decrypted_error) + " Alpha:" + str(alpha))
# 讓日志好看一點(diǎn)
if(iter % 10 == 0):
print()
# 加密誤差達(dá)到一定水平后停止訓(xùn)練
if(encrypted_error < 25000000):
break
print("\n最終預(yù)測(cè):")
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("真預(yù)測(cè):" + str(output_dataset[row_i]) + " 加密預(yù)測(cè):" + str(layer_2) + " 解密預(yù)測(cè):" + str(s_decrypt(layer_2) / scaling_factor))
迭代0 加密損失:84890656 解密損失:2.529Alpha:0.015
迭代10 加密損失:69494197 解密損失:2.071Alpha:0.015
迭代20 加密損失:64017850 解密損失:1.907Alpha:0.015
迭代30 加密損失:62367015 解密損失:1.858Alpha:0.015
迭代40 加密損失:61874493 解密損失:1.843Alpha:0.015
迭代50 加密損失:61399244 解密損失:1.829Alpha:0.015
迭代60 加密損失:60788581 解密損失:1.811Alpha:0.015
迭代70 加密損失:60327357 解密損失:1.797Alpha:0.015
迭代80 加密損失:59939426 解密損失:1.786Alpha:0.015
迭代90 加密損失:59628769 解密損失:1.778Alpha:0.015
迭代100 加密損失:59373621 解密損失:1.769Alpha:0.015
迭代110 加密損失:59148014 解密損失:1.763Alpha:0.015
迭代120 加密損失:58934571 解密損失:1.757Alpha:0.015
迭代130 加密損失:58724873 解密損失:1.75Alpha:0.0155
迭代140 加密損失:58516008 解密損失:1.744Alpha:0.015
迭代150 加密損失:58307663 解密損失:1.739Alpha:0.015
迭代160 加密損失:58102049 解密損失:1.732Alpha:0.015
迭代170 加密損失:57863091 解密損失:1.725Alpha:0.015
迭代180 加密損失:55470158 解密損失:1.653Alpha:0.015
迭代190 加密損失:54650383 解密損失:1.629Alpha:0.015
迭代200 加密損失:53838756 解密損失:1.605Alpha:0.015
迭代210 加密損失:51684722 解密損失:1.541Alpha:0.015
迭代220 加密損失:54408709 解密損失:1.621Alpha:0.015
迭代230 加密損失:54946198 解密損失:1.638Alpha:0.015
迭代240 加密損失:54668472 解密損失:1.63Alpha:0.0155
迭代250 加密損失:55444008 解密損失:1.653Alpha:0.015
迭代260 加密損失:54094286 解密損失:1.612Alpha:0.015
迭代270 加密損失:51251831 解密損失:1.528Alpha:0.015
迭代276 加密損失:24543890 解密損失:0.732Alpha:0.015
最終預(yù)測(cè):
真實(shí)預(yù)測(cè):[0] 加密預(yù)測(cè):[-3761423723.07182550.0] 解密預(yù)測(cè):[-0.112]
真實(shí)預(yù)測(cè):[1] 加密預(yù)測(cè):[24204806753.1662670.0] 解密預(yù)測(cè):[ 0.721]
真實(shí)預(yù)測(cè):[1] 加密預(yù)測(cè):[23090462896.170280.0] 解密預(yù)測(cè):[ 0.688]
真實(shí)預(yù)測(cè):[0] 加密預(yù)測(cè):[1748380342.45533540.0] 解密預(yù)測(cè):[ 0.052]
以上是我訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí)看到的輸出。加密噪聲的某種組合和低精度導(dǎo)致某種程度上笨重的學(xué)習(xí),因此調(diào)優(yōu)具有一定的技巧性。訓(xùn)練也相當(dāng)慢。這些很大程度上是因?yàn)檗D(zhuǎn)置運(yùn)算非常昂貴。我比較確定本可以通過(guò)更簡(jiǎn)單的操作完成轉(zhuǎn)置運(yùn)算,但是,如前所述,像這樣證明概念可行的代碼,我更偏向安全性。
小小的總結(jié)
網(wǎng)絡(luò)的權(quán)重都是加密的。
數(shù)據(jù)解密為1和0.
經(jīng)過(guò)訓(xùn)練后,可以解密網(wǎng)絡(luò),以提高性能或進(jìn)行進(jìn)一步的訓(xùn)練(或者轉(zhuǎn)用不同的加密鑰)。
訓(xùn)練損失和輸出預(yù)測(cè)同樣是加密過(guò)的值。我們需要解碼之后才能解讀網(wǎng)絡(luò)表現(xiàn)。
九、情感分類
下面是一個(gè)真實(shí)一些的例子,我們?cè)贗MDB評(píng)論情感數(shù)據(jù)上訓(xùn)練同態(tài)加密的網(wǎng)絡(luò),網(wǎng)絡(luò)基于Udacity的深度學(xué)習(xí)課程。完整代碼發(fā)布在GitHub上。
import time
import sys
import numpy as np
# 調(diào)整之前的網(wǎng)絡(luò)以建模這些現(xiàn)象
classSentimentNetwork:
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] <= -polarity_cutoff)):
review_vocab.add(word)
else:
review_vocab.add(word)
self.review_vocab = list(review_vocab)
label_vocab = set()
for label in labels:
label_vocab.add(label)
self.label_vocab = list(label_vocab)
self.review_vocab_size = len(self.review_vocab)
self.label_vocab_size = len(self.label_vocab)
self.word2index = {}
for i, word in enumerate(self.review_vocab):
self.word2index[word] = i
self.label2index = {}
for i, label in enumerate(self.label_vocab):
self.label2index[label] = i
def init_network(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
# 設(shè)置輸入層、隱藏層、輸出層節(jié)點(diǎn)數(shù)
self.input_nodes = input_nodes
self.hidden_nodes = hidden_nodes
self.output_nodes = output_nodes
print("Initializing Weights...")
self.weights_0_1_t = np.zeros((self.input_nodes,self.hidden_nodes))
self.weights_1_2_t = np.random.normal(0.0, self.output_nodes**-0.5,
(self.hidden_nodes, self.output_nodes))
print("Encrypting Weights...")
self.weights_0_1 = list()
for i,row in enumerate(self.weights_0_1_t):
sys.stdout.write("\rEncrypting Weights from Layer 0 to Layer 1:" + str(float((i+1) * 100) / len(self.weights_0_1_t))[0:4] + "% done")
self.weights_0_1.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))
print("")
self.weights_1_2 = list()
for i,row in enumerate(self.weights_1_2_t):
sys.stdout.write("\rEncrypting Weights from Layer 1 to Layer 2:" + str(float((i+1) * 100) / len(self.weights_1_2_t))[0:4] + "% done")
self.weights_1_2.append(one_way_encrypt_vector(row,scaling_factor).astype('int64'))
self.weights_1_2 = transpose(self.weights_1_2)
self.learning_rate = learning_rate
self.layer_0 = np.zeros((1,input_nodes))
self.layer_1 = np.zeros((1,hidden_nodes))
def sigmoid(self,x):
return1 / (1 + np.exp(-x))
def sigmoid_output_2_derivative(self,output):
return output * (1 - output)
def update_input_layer(self,review):
# 清除之前的狀態(tài),重置層至全0
self.layer_0 *= 0
for word in review.split(" "):
self.layer_0[0][self.word2index[word]] = 1
def get_target_for_label(self,label):
if(label == 'POSITIVE'):
return1
else:
return0
def train(self, training_reviews_raw, training_labels):
training_reviews = list()
for review in training_reviews_raw:
indices = set()
for word in review.split(" "):
if(word in self.word2index.keys()):
indices.add(self.word2index[word])
training_reviews.append(list(indices))
layer_1 = np.zeros_like(self.weights_0_1[0])
start = time.time()
correct_so_far = 0
total_pred = 0.5
for i in range(len(training_reviews_raw)):
review_indices = training_reviews[i]
label = training_labels[i]
layer_1 *= 0
for index in review_indices:
layer_1 += self.weights_0_1[index]
layer_1 = layer_1 / float(len(review_indices))
layer_1 = layer_1.astype('int64') # 取整至最接近的整數(shù)
layer_2 = sigmoid(innerProd(layer_1,self.weights_1_2[0],M_onehot[len(layer_1) - 2][1],l) / float(scaling_factor))[0:2]
if(label == 'POSITIVE'):
layer_2_delta = layer_2 - (c_ones[len(layer_2) - 2] * scaling_factor)
else:
layer_2_delta = layer_2
weights_1_2_trans = transpose(self.weights_1_2)
layer_1_delta = mat_mul_forward(layer_2_delta,weights_1_2_trans,scaling_factor).astype('int64')
self.weights_1_2 -= np.array(outer_product(layer_2_delta,layer_1)) * self.learning_rate
for index in review_indices:
self.weights_0_1[index] -= (layer_1_delta * self.learning_rate).astype('int64')
# 我們將即時(shí)解密,以便查看發(fā)生了什么
total_pred += (s_decrypt(layer_2)[0] / scaling_factor)
if((s_decrypt(layer_2)[0] / scaling_factor) >= (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):
# 輸入層
# 隱藏層
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]
# 輸出層
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%
....
十、相比數(shù)據(jù)加密的優(yōu)勢(shì)
和這一做法最相似的是加密訓(xùn)練數(shù)據(jù),然后在加密數(shù)據(jù)上訓(xùn)練神經(jīng)網(wǎng)絡(luò)(接受加密輸入并預(yù)測(cè)加密輸出)。這是一個(gè)出色的想法。然而,其實(shí)它有一些缺陷。首先也是最重要的,加密數(shù)據(jù)意味著對(duì)任何不具有加密數(shù)據(jù)的私鑰的人而言,該神經(jīng)網(wǎng)絡(luò)完全無(wú)用。這樣就不可能在不同的私有數(shù)據(jù)源上訓(xùn)練同一深度學(xué)習(xí)模型了。大部分商業(yè)應(yīng)用有這樣的需求,需要匯總消費(fèi)者的數(shù)據(jù)。理論上,我們本來(lái)想要讓每個(gè)消費(fèi)者用他們自己的密鑰保護(hù)自己的數(shù)據(jù),然而同態(tài)加密數(shù)據(jù)要求所有人使用相同的鑰。
而加密網(wǎng)絡(luò)則沒(méi)有這個(gè)限制。
基于上述方法,你可以訓(xùn)練一個(gè)平常的、解密的神經(jīng)網(wǎng)絡(luò)一段時(shí)間,加密它,將它和相應(yīng)的公鑰發(fā)給A方(A方可以基于其所有的數(shù)據(jù)訓(xùn)練網(wǎng)絡(luò)一段時(shí)間……A方保留數(shù)據(jù))。接著,你可以收回這個(gè)網(wǎng)絡(luò),解密它,用另一個(gè)鑰加密網(wǎng)絡(luò),然后發(fā)給B方,B方在其所有的數(shù)據(jù)上進(jìn)行一些訓(xùn)練。由于網(wǎng)絡(luò)自身被加密了,你可以完全控制全過(guò)程中你刻畫的智能。A方和B方將無(wú)法知道他們各自收到的是同一個(gè)網(wǎng)絡(luò),也無(wú)法知道之前見過(guò)這個(gè)網(wǎng)絡(luò),或在自己的數(shù)據(jù)上用過(guò)這個(gè)網(wǎng)絡(luò)。你的公司保留對(duì)神經(jīng)網(wǎng)絡(luò)中的知識(shí)產(chǎn)權(quán)的控制,而每個(gè)用戶保留對(duì)他們自己的數(shù)據(jù)的控制。
十一、以后的工作
存在更快、更安全的同態(tài)加密算法。我相信將本項(xiàng)工作移植到Y(jié)ASHE會(huì)是一個(gè)正確的方向。由于一些系統(tǒng)復(fù)雜性,也許開發(fā)一個(gè)能讓用戶更簡(jiǎn)單地進(jìn)行加密的框架會(huì)是一個(gè)好主意。一般而言,為了達(dá)到生產(chǎn)環(huán)境要求的質(zhì)量,HE需要變得更快。然而,這方面的進(jìn)展十分迅速。我確信我們會(huì)在不久的將來(lái)達(dá)到這一點(diǎn)。
十二、潛在應(yīng)用
分布式AI商業(yè)公司可以分布式地部署它們的模型(用于訓(xùn)練或使用),而無(wú)需冒智能被竊的風(fēng)險(xiǎn)。
保護(hù)消費(fèi)者隱私之前的應(yīng)用提供了這樣的可能性:消費(fèi)者保留他們的數(shù)據(jù),選擇不同的模型在自己的設(shè)備上訓(xùn)練,不用將數(shù)據(jù)發(fā)送到別處。如果商業(yè)公司的智能在分布式場(chǎng)景中沒(méi)有被竊的風(fēng)險(xiǎn),那么他們不尊重消費(fèi)者隱私的借口就會(huì)少很多。數(shù)據(jù)就是力量,它需要回歸到人們手中。
受控超智能網(wǎng)絡(luò)可以充分發(fā)展其智能,不過(guò)除非它具有密鑰,否則它只能預(yù)測(cè)亂碼。
-
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5503瀏覽量
121162 -
同態(tài)加密
+關(guān)注
關(guān)注
1文章
5瀏覽量
1916
原文標(biāo)題:基于Numpy實(shí)現(xiàn)同態(tài)加密神經(jīng)網(wǎng)絡(luò)
文章出處:【微信號(hào):jqr_AI,微信公眾號(hào):論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論