基於Numpy實現同態加密神經網路
編者按:在分散式AI環境下,同態加密神經網路有助於保護商業公司知識產權和消費者隱私。讓我們和DeepMind數據科學家、Udacity深度學習導師Andrew Trask一起,來看看如何基於Numpy實現同態加密神經網路吧。
原文地址: Building Safe A.I.
聲明:感謝原作者Andrew Trask授權論智編譯,未經授權請勿轉載。
TLDR: 在這篇文章中,我們將訓練一個在訓練階段完全加密的神經網路(在未加密的數據上訓練)。得到的神經網路將具備兩個有益的性質。首先,保護神經網路的智能免遭竊取,使有價值的AI可以在不安全的環境中加以訓練而不用冒智能遭竊的風險。其次,網路只能進行加密預測(大概對外部世界毫無影響,因為在沒有密鑰的情況下,外部世界無法理解預測)。這在用戶和超智能間構成了一個有價值的權力失衡。如果AI是同態加密的,那麼在AI看來,整個外部世界也是同態加密的。一個控制密鑰的人類可以選擇解鎖AI本身(將AI釋放到世界中)或僅僅解密AI做出的單個預測(看起來更安全)。
當我寫完新文章後,我通常會發推說一下。如果你對我的文章感興趣,歡迎關注 @iamtrask,也歡迎向我反饋。
如果你對訓練加密神經網路感興趣,可以看看OpenMined的PySyft庫。
超智能
很多人都擔憂超智能有一天會選擇傷害人類。史蒂芬·霍金曾呼籲建立新的世界政府來管理我們賦予人工智慧的能力,以防人工智慧最終摧毀人類。這些是相當大膽的主張,我認為它們反映了科學界和整個世界對這一問題的普遍擔憂。本文將是一篇介紹解決這一問題的潛在技術方案的教程,我將通過一些玩具樣例代碼來演示這一方法。
目標很簡單。我們想要創建未來會變得非常智能的AI技術(智能到可以解決治癒癌症、終結世界上的飢餓等問題),但是這樣的智能受人類的控制(基於密鑰),因而其智能的應用是受限的。不受限的學習是很棒的,但知識的不受限的應用可能具有潛在危險性。
為了介紹這一想法,讓我先簡要介紹兩個非常激動人心的研究領域:深度學習和同態加密。
一、什麼是深度學習?
深度學習是用於自動化智能的工具套件,主要基於神經網路。這一計算機科學的領域,是最近AI技術爆發的主要動力,因為深度學習在許多智能任務上超越了先前的表現記錄。例如,他是DeepMind的AlphaGo系統的主要組成部分。
神經網路基於輸入做出預測。它通過試錯法學習做出有效的預測。剛開始,它做出一個預測(起初基本上是隨機預測),接著接收一個「錯誤信號」,該信號表明它的預測過高或過低(通常是概率)。在這一周期重複數百萬次後,網路開始搞明白情況。想要了解更多神經網路如何工作的細節,請參考基於Numpy實現神經網路:反向傳播一文。
這裡最神奇的是錯誤信號。如果不告知預測的表現有多好,神經網路無法學習。牢記這一點。
二、什麼是同態加密?
顧名思義,同態加密是一種加密的形式。在不對稱情形下,它可以接受完全可讀的文本,然後基於「公鑰」將其轉變為亂碼。更重要的是,它可以基於「私鑰」將亂碼轉回同樣的文本。然而,除非你有「私鑰」,(理論上)你無法解碼加密後的亂碼。
同態加密是一種特殊形式的加密。它允許某人在無法閱讀信息的前提下以特定的方式修改加密信息。例如,同態加密可以應用於數字上,讓加密過的數字可以進行乘法和加法運算而無需解密數字。下面是一些玩具樣例。
現在出現了越來越多的同態加密方案,各有不同的性質。這是一個相對年輕的領域,仍有一些明顯的問題有待解決,不過我們將這些內容留待以後討論。
就目前而言,讓我們從整數公鑰加密方案開始。整數公鑰加密方案是一種乘法和加法上的同態加密,允許進行上圖的操作。不僅如此,由於公鑰允許「單向」加密,你甚至可以進行未加密數字和加密數字間的運算(通過單向加密),上圖的2 * Cypher A
就是一個例子。(某些加密方案甚至不要求這一點……不過同樣……我們以後討論這個。)
三、我們可以結合這兩者嗎?
也許深度學習和同態加密之間最頻繁的互動體現在數據隱私上。當你同態加密數據時,你無法讀取數據但仍然可以保持大多數有趣的統計學結構。這讓人們得以在加密數據上訓練模型(CryptoNets)。甚至有一家名為Numer.ai的初創對沖基金加密昂貴的專有數據,允許任何人嘗試訓練機器學習模型預測股票市場。通常這不可能辦到,因為會導致放棄極為昂貴的信息(不可能基於通常的加密數據訓練模型)。
然而,本文將反其道而行,加密神經網路,然後在解密信息上加以訓練。
複雜度驚人的神經網路,事實上可以劃分成很少(少得驚人)幾種組件,這些組件不斷重複以構成神經網路。其實,僅僅基於如下操作,就可以創建很多最先進的神經網路:
- 加法
- 乘法
- 除法
- 減法
- Sigmoid
- Tanh
- 指數函數
那麼,讓我們提出這一明顯的技術問題,我們能否同態加密神經網路本身?我們會想這麼做嗎?結果發現,基於一些保守的逼近,這是可以辦到的。
- 加法 —— 開箱即用
- 乘法 —— 開箱即用
- 除法 —— 開箱即用?只是乘法的倒數
- 加法 —— 開箱即用?只是加上負數
- Sigmoid —— 嗯……也許有點難度
- Tanh —— 嗯……也許有點難度
- 指數函數 —— 嗯……也許有點難度
看起來實現除法和減法會是相當微不足道的事情,但那些更複雜的函數就……好吧……比簡單的加法和乘法更複雜。為了嘗試同態加密一個深度神經網路,我們還需要一個秘密原料。
四、泰勒級數展開
也許你在小學學過,泰勒級數允許我們使用無限項加法、減法、乘法、除法來計算一個複雜(非線性)函數。這很完美!(除了無限部分)。幸運的是,如果你早早地停止了計算精確的泰勒級數展開,你仍然能得到手頭的函數的一個逼近值。下面是通過泰勒級數逼近一些流行函數的例子(來源)。
等下!這裡有指數!別擔心。指數不過是反覆相乘。下面是使用泰勒級數逼近sigmoid函數的python實現(相關公式見Wolfram Alpha)。我們將選取級數的開始幾項,看看能逼近到什麼程度。
import numpy as npdef sigmoid_exact(x): return 1 / (1 + np.exp(-x))# 使用泰勒級數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("
輸入:" + str(lil_number)) print("精確的Sigmoid值:" + str(sigmoid_exact(lil_number))) print("逼近Sigmoid:" + str(sigmoid_approximation(lil_number)))
結果:
輸入: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
僅僅使用了泰勒級數的前4項,我們已經相當逼近sigmoid函數了。既然我們已經具備了通用的策略,是時候選擇一個同態加密演算法了。
五、選擇加密演算法
同態加密是一個相對較新的領域,其中的主要里程碑是Craig Gentry在2009年發現的第一個全同態加密演算法。這一里程碑為許多後來者建立了據點。大部分關於同態加密的激動人心的研究圍繞開發圖靈完備的同態加密計算機展開。因此,對全同態加密方案的需求讓人們試圖找到一個演算法,使得進行任意計算所需的多種邏輯門都可以通這一演算法高效而安全地計算。大體的希望是人們能夠安全地將工作放到雲端,而不必冒發送到雲端的數據被發送者以外的人讀取的風險。這是一個非常酷的想法,也取得了很多進展。
然而,這一角度存在一些缺陷。一般而言,相比普通電腦,大多數全同態加密方案慢得讓人懷疑人生(目前還不實用)。這鼓舞了一系列有趣的研究,將操作種類限制為某種程度上同態,這樣至少可以進行某些操作。不那麼靈活,但是更快,這是常見的計算上的折衷。
這是我們想要開始查看的地方。理論上,我們想要一個操作浮點數的同態加密方案(不過很快我們將看到,最終我們選擇了操作整數的方案),而不是操作二進位值的方案。二進位可以工作,但它不僅要求全同態加密的靈活性(以性能為代價),還要求我們管理二進位表示和我們想要計算的數學運算之間的邏輯。一個不那麼強大,為浮點運算定製的HE(HE為同態加密Homomorphic Encryption的縮寫)演算法會更合適。
儘管加上了這一限制,仍有非常多的選擇。下面是一些具備我們需要的特性的流行演算法:
- 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是流行的CryptoNet使用的演算法,對浮點運算的支持很棒。然而,它相當複雜。為了讓這篇文章容易閱讀、便於嘗試,我們將選擇稍微不那麼高級(可能也不那麼安全)的Efficient Integer Vector Homomorphic Encryption(高效整數向量同態加密)。然而,我認為非常值得指出的是,在你閱讀本文的時候,更多新的HE演算法正在開發之中,同時本文展示的想法通用於任何在整數或浮點數的加法和乘法上同態加密的方案。甚至說,我的願望是引起對HE的這一應用的關注,以便更多的為深度學習優化的HE演算法能被開發出來。
Yu、Lai、Paylor的論文Efficient Integer Vector Homomorphic Encryption詳細描述了這一演算法,相應的實現可以在GitHub上獲取(jamespayor/vector-homomorphic-encryption)。主要部分在C++文件vhe.cpp
中。下面我們引導讀者閱讀代碼的一個python移植,說明代碼是幹什麼的。如果你選擇實現一個更高級的方案,這也會很有幫助,因為有一些主題相對而言是通用的(一般函數名,變數名,等等)。
六、Python中的同態加密
首先是一些同態加密術語:
- 明文(plaintext) 未加密數據。也叫「消息」。在我們的例子中,這將是一些表示神經網路的數字。
- 密文(cyphertext) 加密數據。我們將在密文之上進行數學運算,這些運算會改變底層的明文。
- 公鑰(public key) 偽隨機數字序列,讓任何人得以加密數據。可以和別人分享,因為(理論上)公鑰只能用於加密。
- 私鑰/密鑰(private/secret key) 偽隨機數字序列,讓你解密被公鑰加密的數據。你不想和別人分享私鑰。否則,別人可以解密你的消息。
對應的變數名(不同的同態加密技術都傾向於使用這些標準變數名):
- S 表示密鑰/私鑰的矩陣。用於解密。
- M 公鑰。用於加密和進行數學運算。在有些演算法中,不是所有數學運算都需要公鑰。但這一演算法非常廣泛地使用公鑰。
- c 加密數據向量,密文。
- x 消息,即明文。有些論文使用
m
作明文的變數名。 - w 單個「加權(weighting)」標量變數,用於重加權輸入消息x(讓它一致地更長或更短)。這一變數用於調節信噪比。加強信號後,對於給定的操作而言,消息較不容易受雜訊影響。然而,過於加強信號,會增加完全毀壞數據的概率。這是一個平衡。
- E或e 一般指隨機雜訊。在某些情形下,指用公鑰加密數據前添加的雜訊。一般而言,雜訊使解密更困難。雜訊使同一消息的兩次加密可以不一樣,在讓消息難以破解方面,這很重要。注意,取決於演算法和實現,這可能是一個向量,也可能是一個矩陣。在其他情形下,指隨操作積累的雜訊,詳見後文。
和許多數學論文的慣用法一樣,大寫字母對應矩陣,小寫字母對應向量,斜體小寫對應標量。我們關注同態加密的四種操作:公私鑰對生成,單向加密,解密,數學運算。讓我們從解密開始。
左邊的公式描述了密鑰S和消息x的一般關係。右邊的公式顯示了如何使用密鑰解密消息。不知道你注意到沒有,右邊的公式並不包含e
。基本上,同態加密技術一般引入足夠多的雜訊使沒有密鑰的情況下難以破解出原始消息,但是引入的雜訊的量又足夠少,當你確實具有密鑰時雜訊可以通過取整忽略。右邊的公式中的框表示「取整到最接近的整數」。其他同態加密演算法使用不同的取整。模數運算幾乎普遍存在。而加密則生成使上述關係為真的c. 如果S是一個隨機矩陣,那麼c很難解密。一個簡單的、非對稱的生成加密鑰的方式是找到密鑰的逆矩陣。讓我們看下相應的Python代碼。
import numpy as npdef generate_key(w,m,n): S = (np.random.rand(m,n) * w / (2 ** 16)) # 可證明 max(S) < w return Sdef 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 cdef decrypt(c,S,w): return (S.dot(c) / w).astype(int)x = np.array([0,1,2,5])m = len(x)n = mw = 16S = generate_key(w,m,n)
你可以在Jupyter Notebook中試著運行上面的代碼,進行一些操作:
注意,我們可以對密文進行一些基本的運算,這些運算改動了相應的明文。很優雅,不是嗎?
七、優化加密
重要一課: 回顧一下之前的公式。如果密鑰S是一個單位矩陣,那麼c不過是輸入x的一個重加權的、略帶雜訊的版本。如果你不明白上面的話,請Google「單位矩陣教程」。限於篇幅,這裡就不詳細介紹單位矩陣了。
這引導我們思考加密是如何進行的。論文作者沒有顯式地分配一對獨立的「公鑰」和「私鑰」,相反,提出了一種「鑰交換」技術,將私鑰S替換為S』。更具體地,這一私鑰交換技術涉及生成一個可以進行該變換的矩陣M。由於M具備將消息從未加密狀態(單位矩陣密鑰)轉換為加密狀態(隨機而難以猜測的密鑰),這個M矩陣正好可以用作我們的公鑰!
上面一段話包含許多信息,我們也許講得太快了。讓我們重新概括一下。
發生了什麼……
- 基於上面兩個公式,如果密鑰是一個單位矩陣,那麼消息是未加密的。
- 基於上面兩個公式,如果密鑰是一個隨機矩陣,那麼消息是加密的。
- 我們構造一個矩陣M將一個密鑰轉換為另一個私鑰。
- 當矩陣M將單位矩陣轉換為一個隨機密鑰時,根據定義,它使用單向加密方式加密了消息。
- 由於M充當了「單向加密」的角色,我們稱它為「公鑰」,並且可以像公鑰一樣分發它,因為它無法用於解密。
好了,不多拖延了,讓我們看下這一切是如何通過Python實現的。
import numpy as npdef generate_key(w,m,n): S = (np.random.rand(m,n) * w / (2 ** 16)) # 可證明 max(S) < w return Sdef 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 cdef 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_stardef 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_primedef 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_stardef get_T(n): n_prime = n + 1 T = (10 * np.random.rand(n,n_prime - n)).astype(int) return Tdef encrypt_via_switch(x,w,m,n,T): c,S = switch_key(x*w,np.eye(m),m,n,T) return c,Sx = np.array([0,1,2,5])m = len(x)n = mw = 16S = generate_key(w,m,n)
上面的代碼的基本思路是讓S大體上是單位矩陣,然後在其之上連接一個隨機向量T。因此T具備所有密鑰所需的信息,不過我們仍然需要構建一個尺寸為S的矩陣使得一切可以工作。
八、創建一個XOR神經網路
既然我們已經知道如何加密和解密消息(以及進行基本的加法和乘法計算),是時候嘗試擴展剩餘的運算,以便構建一個簡單的XOR神經網路。儘管從技術上說,神經網路不過是一系列非常簡單的操作,我們還是需要一些操作的組合以實現便利的功能。下面我將描述我們需要的每項操作,以及在一個較高的抽象層次上,我們是如何實現這些操作的(基本上是我們將使用的加法和乘法的序列)。接著我會向你展示代碼。關於一些細節,請參考前面提到的論文。
- 浮點數 我們將簡單地scale浮點數到整數。我們將在整數上訓練我們的網路(把整數當成浮點數)。比如,假設
scale=1000
,0.2 * 0.5 = 0.1
就是200 * 500 = 100000
。還原時,100000 / (1000 * 1000) = 0.1
(因為我們使用了乘法,所以需要除以1000的平方)。初看起來這很有技巧性,但你會適應的。由於我們使用的HE方案取整到最接近的整數,這也讓我們得以控制神經網路的精度。 - 向量矩陣乘法 這是我們的黃油麵包(最基本的操作)。事實上,轉換密鑰的矩陣M是一種線性變換的方式。
- 內積 在合適的背景下,上述線性變換可能是內積。
- sigmoid 由於我們可以進行向量矩陣乘法運算,基於足夠的乘法,我們可以演算任意多項式的值。因為我們已經知道了對應sigmoid的泰勒級數多項式,我們可以演算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_resultdef 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編碼向量。這有助於上面的掩碼操作,以及其他我們希望可以做到的簡單操作。例如,sigmoid的導數是sigmoid(x) * (1 - sigmoid(x))
。因此,預計算這些變數會很方便。下面是預計算步驟。
# 在安全的服務端進行l = 100w = 2 ** 25aBound = 10tBound = 10eBound = 10max_dim = 10scaling_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.5H_sigmoid_txt[0][1] = 0.25H_sigmoid_txt[0][2] = -1/48.0H_sigmoid_txt[0][3] = 1/480.0H_sigmoid_txt[0][4] = -17/80640.0H_sigmoid = list()for row in H_sigmoid_txt: H_sigmoid.append(one_way_encrypt_vector(row))
如果你仔細查看了上面的代碼,你會注意到H_sigmoid
矩陣是我們需要的用於演算sigmoid多項式的矩陣。最後,我們使用如下代碼訓練我們的神經網路。如果不明白神經網路的部分,你可以溫習下基於Numpy實現神經網路:反向傳播一文。我基本上使用了文中的XOR網路,使用適當的工具函數替換了其中一些操作,以加密權重。
np.random.seed(1234)input_dataset = [[],[0],[1],[0,1]]output_dataset = [[0],[1],[1],[0]]input_dim = 3hidden_dim = 4output_dim = 1alpha = 0.015# 使用公鑰單向加密訓練數據(可就地進行)y = list()for i in range(4): y.append(one_way_encrypt_vector(output_dataset[i],scaling_factor))# 生成權重syn0_t = (np.random.randn(input_dim,hidden_dim) * 0.2) - 0.1syn1_t = (np.random.randn(output_dim,hidden_dim) * 0.2) - 0.1# 單向加密權重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))# 開始訓練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) # 如果有安全性要求,可以將加密的損失發送到別處解密。 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("
迭代" + str(iter) + " 加密損失:" + str(encrypted_error) + " 解密損失:" + str(decrypted_error) + " Alpha:" + str(alpha)) # 讓日誌好看一點 if(iter % 10 == 0): print() # 加密誤差達到一定水平後停止訓練 if(encrypted_error < 25000000): breakprint("
最終預測:")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("真預測:" + str(output_dataset[row_i]) + " 加密預測:" + str(layer_2) + " 解密預測:" + str(s_decrypt(layer_2) / scaling_factor))迭代0 加密損失:84890656 解密損失:2.529 Alpha:0.015 迭代10 加密損失:69494197 解密損失:2.071 Alpha:0.015 迭代20 加密損失:64017850 解密損失:1.907 Alpha:0.015 迭代30 加密損失:62367015 解密損失:1.858 Alpha:0.015 迭代40 加密損失:61874493 解密損失:1.843 Alpha:0.015 迭代50 加密損失:61399244 解密損失:1.829 Alpha:0.015 迭代60 加密損失:60788581 解密損失:1.811 Alpha:0.015 迭代70 加密損失:60327357 解密損失:1.797 Alpha:0.015 迭代80 加密損失:59939426 解密損失:1.786 Alpha:0.015 迭代90 加密損失:59628769 解密損失:1.778 Alpha:0.015 迭代100 加密損失:59373621 解密損失:1.769 Alpha:0.015 迭代110 加密損失:59148014 解密損失:1.763 Alpha:0.015 迭代120 加密損失:58934571 解密損失:1.757 Alpha:0.015 迭代130 加密損失:58724873 解密損失:1.75 Alpha:0.0155 迭代140 加密損失:58516008 解密損失:1.744 Alpha:0.015 迭代150 加密損失:58307663 解密損失:1.739 Alpha:0.015 迭代160 加密損失:58102049 解密損失:1.732 Alpha:0.015 迭代170 加密損失:57863091 解密損失:1.725 Alpha:0.015 迭代180 加密損失:55470158 解密損失:1.653 Alpha:0.015 迭代190 加密損失:54650383 解密損失:1.629 Alpha:0.015 迭代200 加密損失:53838756 解密損失:1.605 Alpha:0.015 迭代210 加密損失:51684722 解密損失:1.541 Alpha:0.015 迭代220 加密損失:54408709 解密損失:1.621 Alpha:0.015 迭代230 加密損失:54946198 解密損失:1.638 Alpha:0.015 迭代240 加密損失:54668472 解密損失:1.63 Alpha:0.0155 迭代250 加密損失:55444008 解密損失:1.653 Alpha:0.015 迭代260 加密損失:54094286 解密損失:1.612 Alpha:0.015 迭代270 加密損失:51251831 解密損失:1.528 Alpha:0.015 迭代276 加密損失:24543890 解密損失:0.732 Alpha:0.015 最終預測:真實預測:[0] 加密預測:[-3761423723.0718255 0.0] 解密預測:[-0.112]真實預測:[1] 加密預測:[24204806753.166267 0.0] 解密預測:[ 0.721]真實預測:[1] 加密預測:[23090462896.17028 0.0] 解密預測:[ 0.688]真實預測:[0] 加密預測:[1748380342.4553354 0.0] 解密預測:[ 0.052]
以上是我訓練神經網路時看到的輸出。加密雜訊的某種組合和低精度導致某種程度上笨重的學習,因此調優具有一定的技巧性。訓練也相當慢。這些很大程度上是因為轉置運算非常昂貴。我比較確定本可以通過更簡單的操作完成轉置運算,但是,如前所述,像這樣證明概念可行的代碼,我更偏向安全性。
小小的總結
- 網路的權重都是加密的。
- 數據解密為1和0.
- 經過訓練後,可以解密網路,以提高性能或進行進一步的訓練(或者轉用不同的加密鑰)。
- 訓練損失和輸出預測同樣是加密過的值。我們需要解碼之後才能解讀網路表現。
九、情感分類
下面是一個真實一些的例子,我們在IMDB評論情感數據上訓練同態加密的網路,網路基於Udacity的深度學習課程。完整代碼發布在GitHub上。
(字數超過限制,請前往公眾號或github查看代碼)
十、相比數據加密的優勢
和這一做法最相似的是加密訓練數據,然後在加密數據上訓練神經網路(接受加密輸入並預測加密輸出)。這是一個出色的想法。然而,其實它有一些缺陷。首先也是最重要的,加密數據意味著對任何不具有加密數據的私鑰的人而言,該神經網路完全無用。這樣就不可能在不同的私有數據源上訓練同一深度學習模型了。大部分商業應用有這樣的需求,需要匯總消費者的數據。理論上,我們本來想要讓每個消費者用他們自己的密鑰保護自己的數據,然而同態加密數據要求所有人使用相同的鑰。
而加密網路則沒有這個限制。
基於上述方法,你可以訓練一個平常的、解密的神經網路一段時間,加密它,將它和相應的公鑰發給A方(A方可以基於其所有的數據訓練網路一段時間……A方保留數據)。接著,你可以收回這個網路,解密它,用另一個鑰加密網路,然後發給B方,B方在其所有的數據上進行一些訓練。由於網路自身被加密了,你可以完全控制全過程中你刻畫的智能。A方和B方將無法知道他們各自收到的是同一個網路,也無法知道之前見過這個網路,或在自己的數據上用過這個網路。你的公司保留對神經網路中的知識產權的控制,而每個用戶保留對他們自己的數據的控制。
(字數超過限制,後文已略)
如果你喜歡我們的文章,歡迎關注論智公眾號:jqr_AI
我們每月都有免費送書活動哦~
推薦閱讀:
TAG:神經網路 | 深度學習DeepLearning | numpy |