操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    選自JuliaComputing

    作者:Keno Fischer

    機(jī)器之心編譯

    參與:李詩(shī)萌、Geek AI

    最近,「區(qū)塊鏈」、「聯(lián)邦學(xué)習(xí)」等概念受到了空前的關(guān)注。而在這些概念背后,少不了一項(xiàng)技術(shù)的影子——「同態(tài)加密」。本文介紹了使用 Julia 語言進(jìn)行基于同態(tài)加密數(shù)據(jù)機(jī)器學(xué)習(xí)的全過程,對(duì)于入門者具有極大的參考價(jià)值。


    注意:本文討論了最前沿的密碼學(xué)技術(shù),旨在提供一種利用「Julia Computing」進(jìn)行研究的視角。請(qǐng)不要將文中的任何示例用于生產(chǎn)應(yīng)用程序。在使用密碼學(xué)之前一定要咨詢專業(yè)的密碼學(xué)專家。


    程序包:https://github.com/JuliaComputing/ToyFHE.jl相關(guān)代碼:https://github.com/JuliaComputing/ToyFHE.jl/blob/master/examples/encrypted_mnist/infer.jl


    引言


    假設(shè)你開發(fā)了一個(gè)酷炫的新機(jī)器學(xué)習(xí)模型,現(xiàn)在你想將部署該模型,為用戶提供服務(wù)。應(yīng)該怎么做呢?最簡(jiǎn)單的方法可能是直接把模型發(fā)布給用戶,然后讓他們使用自己的數(shù)據(jù)在本地運(yùn)行這個(gè)模型。但這種方法存在一些問題:


    • 機(jī)器學(xué)習(xí)模型一般都很大,而用戶的設(shè)備實(shí)際上可能沒有足夠的存儲(chǔ)空間或算力來運(yùn)行模型
    • 機(jī)器學(xué)習(xí)模型一般都會(huì)頻繁地更新,你可能不會(huì)想在網(wǎng)絡(luò)上頻繁傳輸這么大的模型
    • 開發(fā)機(jī)器學(xué)習(xí)模型需要大量時(shí)間和計(jì)算資源,你可能會(huì)想通過向使用該模型的用戶收費(fèi)來收回成本


    接下來,常用的解決方案是將模型作為應(yīng)用程序接口(API)在云上公開。在過去幾年間,這些「機(jī)器學(xué)習(xí)即服務(wù)」產(chǎn)品如雨后春筍般涌現(xiàn),每個(gè)主要的云平臺(tái)都會(huì)為企業(yè)級(jí)開發(fā)者提供這樣的服務(wù)。
    但這類產(chǎn)品的潛在用戶所面對(duì)的困境也是顯而易見的——處理用戶數(shù)據(jù)的遠(yuǎn)程服務(wù)器可能并不可信。這樣就會(huì)存在明確的倫理和法律的分歧,從而限制這種解決方案的有效范圍。在受監(jiān)管的產(chǎn)業(yè)(尤其是醫(yī)療業(yè)和金融業(yè))中,一般是不允許將病患或金融數(shù)據(jù)發(fā)送給第三方進(jìn)行處理的。我們可以做得更好嗎?


    事實(shí)證明,我們可以!最近,密碼學(xué)方面取得的突破可以在無需進(jìn)行解密的情況下,直接計(jì)算加密數(shù)據(jù)。在我們的例子中,用戶可以將加密數(shù)據(jù)(例如圖像)傳遞給云 API,以此運(yùn)行機(jī)器學(xué)習(xí)模型,并返回加密的答案。整個(gè)過程中都沒有解密用戶數(shù)據(jù),尤其是云服務(wù)商既不能訪問原始圖像,也不能解碼計(jì)算得到的預(yù)測(cè)值。這是怎么做到的呢?本文通過構(gòu)建一個(gè)進(jìn)行加密圖像的手寫識(shí)別(來自 MNIST 數(shù)據(jù)集)的機(jī)器學(xué)習(xí)模型為大家揭秘背后的原理。


    同態(tài)加密(Homomorphic Encryption,HE)的一般解釋


    一般而言,對(duì)加密數(shù)據(jù)進(jìn)行計(jì)算的能力被稱為「安全計(jì)算」,這是一個(gè)相當(dāng)大的研究領(lǐng)域,針對(duì)大量不同的場(chǎng)景要用不同的密碼學(xué)方法和技術(shù)解決問題。在本例中,我們將關(guān)注所謂的「同態(tài)加密」技術(shù)。在同態(tài)加密系統(tǒng)中,我們一般要進(jìn)行以下操作:

    pub_key,?eval_key,?priv_key?=?keygen()
    encrypted?=?encrypt(pub_key,?plaintext)
    decrypted?=?decrypt(priv_key,?encrypted)
    encrypted′?=?eval(eval_key,?f,?encrypted)
    


    前三步非常直觀,之前使用過任何非對(duì)稱加密技術(shù)的人都會(huì)對(duì)此感到很熟悉(就像通過安全傳輸層協(xié)議(TLS)連接到本文)。最后一步才是神奇之處。它使用加密數(shù)據(jù)評(píng)估了 f,并返回了另一個(gè)與基于加密值評(píng)估 f 的結(jié)果對(duì)應(yīng)的加密值。這一性質(zhì)正是我們將這種技術(shù)稱為「同態(tài)加密」的原因。評(píng)估操作與下面的加密操作等價(jià):


    f(decrypt(priv_key,?encrypted))?==?decrypt(priv_key,?eval(eval_key,?f,?encrypted))
    


    (同樣地,可以基于加密值評(píng)估任意的同態(tài) f)


    支持哪些函數(shù) f 取決于加密方案和支持的運(yùn)算。如果只支持一種函數(shù) f(比如 f=+),我們可以將這種加密方案稱為「部分同態(tài)」。如果 f 是可以建立任意電路的完整的門的集合,如果電路大小有限,稱之為「有限同態(tài)」(Somewhat Homomorphic Encryption, SHE);如果電路大小不受限制,稱之為「全同態(tài)」(Fully Homomorphic Encryption, FHE)。一般可以通過自助法(bootstrapping),將「有限」同態(tài)轉(zhuǎn)換為「全」同態(tài),但這個(gè)問題已經(jīng)超過了本文所討論的內(nèi)容。
    全同態(tài)加密是最近的研究,Craig Gentry 在 2009 年發(fā)表了第一個(gè)可行(但不實(shí)際)的方。現(xiàn)在陸續(xù)出現(xiàn)了一些更新也更實(shí)際的 FHE 方案。更重要的是,還有一些可以高效地實(shí)現(xiàn)這一方案的軟件包。最常用的兩個(gè)軟件包是 Microsoft SEAL和 PALISADE。此外,我最近還開源了這些算法的 Julia 實(shí)現(xiàn)(https://github.com/JuliaComputing/ToyFHE.jl)。出于我們的目的,我們將使用后者中實(shí)現(xiàn)的 CKKS 加密。


    高級(jí) CKKS


    CKKS(以 Cheon-Kim-Kim-Song 的名字命名,他在 2016 年的論文「Homomorphic Encryption for Arithmetic of Approximate Numbers」提出)是一種同態(tài)加密方案,可以對(duì)以下基本操作進(jìn)行同態(tài)評(píng)估:


    • 長(zhǎng)度為 n 的復(fù)數(shù)向量的對(duì)應(yīng)元素相加
    • 長(zhǎng)度為 n 的復(fù)數(shù)向量的對(duì)應(yīng)元素相乘
    • 向量中元素的旋轉(zhuǎn)(通過循環(huán)移位實(shí)現(xiàn))
    • 向量元素的復(fù)共軛


    這里的參數(shù) n 取決于需要的安全性和準(zhǔn)確性,該值一般都比較高。在本例中,n=4096(值越高越安全,但是計(jì)算開銷也更大,時(shí)間復(fù)雜度大致會(huì)縮放為 nlog^n)。


    此外,用 CKKS 計(jì)算是有噪聲的。因此,計(jì)算結(jié)果一般都只是近似值,而且要注意確保評(píng)估結(jié)果足夠準(zhǔn)確,不會(huì)影響結(jié)果的正確性。


    也就是說,對(duì)機(jī)器學(xué)習(xí)程序包的開發(fā)者而言,這些限制并不罕見。像 GPU 這樣有特殊用途的加速器,也可以處理數(shù)字向量。同樣,許多開發(fā)者會(huì)因算法選擇的影響、多線程等原因,認(rèn)為浮點(diǎn)數(shù)噪聲太多(我要強(qiáng)調(diào)的是,有一個(gè)關(guān)鍵的區(qū)別是,浮點(diǎn)算法本身是確定性的,盡管因?yàn)閷?shí)現(xiàn)的復(fù)雜性,它有時(shí)不會(huì)展現(xiàn)出這種確定性,但 CKKS 原語的噪聲真的很多,但這也許可以讓用戶意識(shí)到噪聲并沒有第一次出現(xiàn)時(shí)那么可怕)。


    考慮到這一點(diǎn),我們?cè)倏纯慈绾卧?Julia 中執(zhí)行這些運(yùn)算(注意:這里有一些非常不安全的參數(shù)選擇,這些操作的目的是說明這個(gè)庫(kù)在交互式解釋器(REPL)中的用法)。


    julia>?using?ToyFHE
    #?Let's?play?with?8?element?vectors
    julia>?N?=?8;
    #?Choose?some?parameters?-?we'll?talk?about?it?later
    julia>???=?NegacyclicRing(2N,?(40,?40,?*40*))
    ??????????????????????????????????????/(x1??+?1)
    #?We'll?use?CKKS julia>?params?=?CKKSParams(?)
    CKKS?parameters
    #?We?need?to?pick?a?scaling?factor?for?a?numbers?-?again?we'll?talk?about?that?later
    julia>?Tscale?=?FixedRational{2^40}
    FixedRational{1099511627776,T}?where?T
    #?Let's?start?with?a?plain?Vector?of?zeros
    julia>?plain?=?CKKSEncoding{Tscale}(zero(?))
    8-element?CKKSEncoding{FixedRational{1099511627776,T}?where?T}?with?indices?0:7:
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    0.0?+?0.0im
    #?Ok,?we're?ready?to?get?started,?but?first?we'll?need?some?keys
    julia>?kp?=?keygen(params)
    CKKS?key?pair
    julia>?kp.priv
    CKKS?private?key
    julia>?kp.pub
    CKKS?public?key
    #?Alright,?let's?encrypt?some?things:
    julia>?foreach(i->plain[i]?=?i+1,?0:7);?plain
    8-element?CKKSEncoding{FixedRational{1099511627776,T}?where?T}?with?indices?0:7:
    1.0?+?0.0im
    2.0?+?0.0im
    3.0?+?0.0im
    4.0?+?0.0im
    5.0?+?0.0im
    6.0?+?0.0im
    7.0?+?0.0im
    8.0?+?0.0im
    julia>?c?=?encrypt(kp.pub,?plain)
    CKKS?ciphertext?(length?2,?encoding?CKKSEncoding{FixedRational{1099511627776,T}?where?T})
    #?And?decrypt?it?again
    julia>?decrypt(kp.priv,?c)
    8-element?CKKSEncoding{FixedRational{1099511627776,T}?where?T}?with?indices?0:7:
    0.9999999999995506?-?2.7335193113350057e-16im
    1.9999999999989408?-?3.885780586188048e-16im
    3.000000000000205?+?1.6772825551165524e-16im
    4.000000000000538?-?3.885780586188048e-16im
    4.999999999998865?+?8.382500573679615e-17im
    6.000000000000185?+?4.996003610813204e-16im
    7.000000000001043?-?2.0024593503998215e-16im
    8.000000000000673?+?4.996003610813204e-16im
    #?Note?that?we?had?some?noise.?Let's?go?through?all?the?primitive?operations?we'll?need:
    julia>?decrypt(kp.priv,?c+c)
    8-element?CKKSEncoding{FixedRational{1099511627776,T}?where?T}?with?indices?0:7:
    1.9999999999991012?-?5.467038622670011e-16im
    3.9999999999978817?-?7.771561172376096e-16im
    6.00000000000041?+?3.354565110233105e-16im
    8.000000000001076?-?7.771561172376096e-16im
    9.99999999999773?+?1.676500114735923e-16im
    12.00000000000037?+?9.992007221626409e-16im
    14.000000000002085?-?4.004918700799643e-16im
    16.000000000001346?+?9.992007221626409e-16im
    julia>?csq?=?c*c
    CKKS?ciphertext?(length?3,?encoding?CKKSEncoding{FixedRational{1208925819614629174706176,T}?where?T})
    julia>?decrypt(kp.priv,?csq)8-element?CKKSEncoding{FixedRational{1208925819614629174706176,T}?where?T}?with?indices?0:7:
    0.9999999999991012?-?2.350516767363621e-15im
    3.9999999999957616?-?5.773159728050814e-15im
    9.000000000001226?-?2.534464540987068e-15im
    16.000000000004306?-?2.220446049250313e-15im
    24.99999999998865?+?2.0903753311370056e-15im
    36.00000000000222?+?4.884981308350689e-15im
    49.000000000014595?+?1.0182491378134327e-15im
    64.00000000001077?+?4.884981308350689e-15im
    


    這很簡(jiǎn)單!敏銳的讀者可能已經(jīng)注意到了 csq 和之前的密文看起來有點(diǎn)不同。尤其是,它是「長(zhǎng)度為 3」的密文,范圍也更大。要說明它們是什么,以及它們是做什么用的有點(diǎn)太過復(fù)雜。我只想說,在進(jìn)一步計(jì)算之前,我們要得讓這些值降下來,否則我們會(huì)盡密文中的「空間」。幸運(yùn)的是,有一種方法可以解決這兩個(gè)問題:


    #?To?get?back?down?to?length?2,?we?need?to?`keyswitch`?(aka
    #?relinerarize),?which?requires?an?evaluation?key.?Generating
    #?this?requires?the?private?key.?In?a?real?application?we?would
    #?have?generated?this?up?front?and?sent?it?along?with?the?encrypted
    #?data,?but?since?we?have?the?private?key,?we?can?just?do?it?now.
    julia>?ek?=?keygen(EvalMultKey,?kp.priv)
    CKKS?multiplication?key
    julia>?csq_length2?=?keyswitch(ek,?csq)
    CKKS?ciphertext?(length?2,?encoding?CKKSEncoding{FixedRational{1208925819614629174706176,T}?where?T})
    #?Getting?the?scale?back?down?is?done?using?modswitching.
    julia>?csq_smaller?=?modswitch(csq_length2)
    CKKS?ciphertext?(length?2,?encoding?CKKSEncoding{FixedRational{1.099511626783e12,T}?where?T})
    #?And?it?still?decrypts?correctly?(though?note?we've?lost?some?precision)
    julia>?decrypt(kp.priv,?csq_smaller)
    8-element?CKKSEncoding{FixedRational{1.099511626783e12,T}?where?T}?with?indices?0:7:
    0.9999999999802469?-?5.005163520332181e-11im
    3.9999999999957723?-?1.0468514951188039e-11im
    8.999999999998249?-?4.7588542623100616e-12im
    16.000000000023014?-?1.0413447889166631e-11im
    24.999999999955193?-?6.187833723406491e-12im
    36.000000000002345?+?1.860733715346631e-13im
    49.00000000001647?-?1.442396043149794e-12im
    63.999999999988695?-?1.0722489563648028e-10im
    


    此外,modswitching(模轉(zhuǎn)換:modulus switching 的簡(jiǎn)寫)減少了密文模的大小,所以我們不能無限地這么做下去。(用上文提到的術(shù)語來說,我們?cè)谶@里使用的是 SHE 方案):

    julia>???#?Remember?the?ring?we?initially?created
    ??????????????????????????????????????/(x1??+?1)
    
    julia>?ToyFHE.ring(csq_smaller)?#?It?shrunk!
    ??????????????????????????/(x1??+?1)
    


    我們要做的最后一步運(yùn)算是:旋轉(zhuǎn)。就像上文的密鑰轉(zhuǎn)換(KeySwitching),在這里也需要評(píng)估密鑰(也稱為伽羅瓦(galois)密鑰):


    julia>?gk?=?keygen(GaloisKey,?kp.priv;?steps=2)
    CKKS?galois?key?(element?25)
    julia>?decrypt(circshift(c,?gk))
    decrypt(kp,?circshift(c,?gk))
    8-element?CKKSEncoding{FixedRational{1099511627776,T}?where?T}?with?indices?0:7:
    7.000000000001042?+?5.68459112632516e-16im
    8.000000000000673?+?5.551115123125783e-17im
    0.999999999999551?-?2.308655353580721e-16im
    1.9999999999989408?+?2.7755575615628914e-16im
    3.000000000000205?-?6.009767921608429e-16im
    4.000000000000538?+?5.551115123125783e-17im
    4.999999999998865?+?4.133860996136768e-17im
    6.000000000000185?-?1.6653345369377348e-16im
    #?And?let's?compare?to?doing?the?same?on?the?plaintext
    julia>?circshift(plain,?2)
    8-element?OffsetArray(::Array{Complex{Float64},1},?0:7)?with?eltype?Complex{Float64}?with?indices?0:7:
    7.0?+?0.0im
    8.0?+?0.0im
    1.0?+?0.0im
    2.0?+?0.0im
    3.0?+?0.0im
    4.0?+?0.0im
    5.0?+?0.0im
    6.0?+?0.0im
    


    好了,我們已經(jīng)了解了同態(tài)加密庫(kù)的基本用法。在思考如何用這些原語進(jìn)行神經(jīng)網(wǎng)絡(luò)推斷之前,我們先觀察并訓(xùn)練我們需要使用的神經(jīng)網(wǎng)絡(luò)。


    機(jī)器學(xué)習(xí)模型


    如果你不熟悉機(jī)器學(xué)習(xí)或 Flux.jl 機(jī)器學(xué)習(xí)庫(kù),我建議你先快速閱讀一下 Flux.jl 文檔或我們?cè)?JuliaAcademy 上發(fā)布的免費(fèi)機(jī)器學(xué)習(xí)介紹課程,因?yàn)槲覀冎粫?huì)討論在加密數(shù)據(jù)上運(yùn)行模型所做的更改。


    我們將以 Flux 模型空間中卷積神經(jīng)網(wǎng)絡(luò)的例子為出發(fā)點(diǎn)。在這個(gè)模型中,訓(xùn)練循環(huán)、數(shù)據(jù)預(yù)處理等操作都不變,只是輕微地調(diào)整模型。我們要用的模型是:


    function?reshape_and_vcat(x)
        let?y=reshape(x,?64,?4,?size(x,?4))
            vcat((y[:,i,:]?for?i=axes(y,2))...)
        end
    end
    model?=?Chain(
        #?First?convolution,?operating?upon?a?28x28?image
        Conv((7,?7),?1=>4,?stride=(3,3),?x->x.^2),
        reshape_and_vcat,
        Dense(256,?64,?x->x.^2),
        Dense(64,?10),
    )
    


    該模型與「安全外包矩陣的計(jì)算及其在神經(jīng)網(wǎng)絡(luò)上與應(yīng)用」(Secure Outsourced Matrix Computation and Application to Neural Networks)文中所用的模型基本相同,它們用相同的加密方案演示了相同的模型,但有兩個(gè)區(qū)別:(1)他們加密了模型而我們(為簡(jiǎn)單起見)沒有對(duì)模型加密;(2)我們?cè)诿恳粚又蠖加衅孟蛄浚ㄟ@也是 Flux 的默認(rèn)行為),我不確定這種行為對(duì)本文評(píng)估的模型是否是這樣。也許是因?yàn)椋?),我們模型的準(zhǔn)確率才略高(98.6% vs 98.1%),但這也可能僅僅是因?yàn)槌瑓?shù)的差異。


    「x.^2」激活函數(shù)也是一個(gè)不尋常的特征(對(duì)那些有機(jī)器學(xué)習(xí)背景的人來說)。這里更常用的選擇可能是「tanh」、「relu」或者其他更高級(jí)的函數(shù)。然而,盡管這些函數(shù)(尤其是 relu)可以更容易地評(píng)估明文值,但評(píng)估加密數(shù)據(jù)的計(jì)算開銷則相當(dāng)大(基本上是評(píng)估多項(xiàng)式近似值)。幸運(yùn)的是,「x.^2」可以很好地滿足我們的目的。


    其余的訓(xùn)練循環(huán)基本上是相同的。我們從模型中刪除了「softmax」,取而代之的是「logitcrossentropy」損失函數(shù)(當(dāng)然也可以保留它,在客戶端解密后再評(píng)估「softmax」)。訓(xùn)練模型的完整代碼見 GitHub,在近期發(fā)布的 GPU 上只需要幾分鐘就可以完成訓(xùn)練。
    代碼地址:https://github.com/JuliaComputing/ToyFHE.jl/blob/master/examples/encrypted_mnist/train.jl
    高效地計(jì)算


    好了,現(xiàn)在已經(jīng)明確了我們需要做什么,接下來看看我們要做哪些運(yùn)算:


    • 卷積
    • 元素平方
    • 矩陣乘法


    我們?cè)谏衔闹幸呀?jīng)看到了,元素平方操作是很簡(jiǎn)單的,所以我們按順序處理剩下的兩個(gè)問題。在整個(gè)過程中,假設(shè)批處理大小(batch size)為 64(你可能注意到了,我們有策略地選擇模型參數(shù)和批處理大小,從而充分利用 4096 元素向量的優(yōu)勢(shì),這是我們從實(shí)際的參數(shù)選擇中得到的)。


    卷積


    讓我們回顧一下卷積是如何工作的。首先,取原始輸入數(shù)組中的一些窗口(本例中為 7*7),窗口中的每個(gè)元素跟卷積掩模的元素相乘。然后移動(dòng)窗口(本例中步長(zhǎng)為 3,所以將窗口移動(dòng) 3 個(gè)元素)。重復(fù)這個(gè)過程(用相同的卷積掩模)。下面的動(dòng)畫說明了以(2,2)的步長(zhǎng)進(jìn)行 3*3 卷積的過程(藍(lán)色數(shù)組是輸入,綠色數(shù)組是輸出)。



    另外,我們將卷積分成 4 個(gè)不同的「通道」(這意味著用不同的卷積掩模,將卷積又重復(fù)了 3 次)


    好了,現(xiàn)在我們已經(jīng)知道了要做什么,接下來考慮一下該如何實(shí)現(xiàn)。幸運(yùn)的是,卷積是我們模型中的第一步運(yùn)算。因此,可以在加密數(shù)據(jù)之前(無需模型權(quán)重)先在客戶端上預(yù)處理,來節(jié)省一些工作。具體而言,我們將執(zhí)行以下操作:


    • 預(yù)先計(jì)算每個(gè)卷積窗口(即從原始圖像中提取 7*7 的窗口),從每個(gè)輸入圖像中得到 64 個(gè) 7*7 的矩陣(注意要在步長(zhǎng)為 2 的情況下得到 7*7 的窗口,要評(píng)估 28*28 的輸入圖像的話,要計(jì)算 8*8 的卷積窗口)
    • 將每個(gè)窗口中的相同位置收集到一個(gè)向量中,即對(duì)每張圖來說,都會(huì)有包含 64 個(gè)元素的向量,或當(dāng)批處理大小為 64 時(shí),會(huì)得到 64*64 的元素向量(即,共有 49 個(gè) 64*64 的矩陣)
    • 加密


    然后卷積就變成了整個(gè)矩陣和適當(dāng)掩碼元素的標(biāo)量乘法,對(duì)這 49 個(gè)元素求和,得到了卷積的結(jié)果。這個(gè)方案是這樣實(shí)現(xiàn)的(在明文上):


    function?public_preprocess(batch)
        ka?=?OffsetArray(0:7,?0:7)
        #?Create?feature?extracted?matrix
        I?=?[[batch[i′*3?.+?(1:7),?j′*3?.+?(1:7),?1,?k]?for?i′=ka,?j′=ka]?for?k?=?1:64]
        #?Reshape?into?the?ciphertext
        I???=?[[I[k][l...][i,j]?for?k=1:64,?l=product(ka,?ka)]?for?i=1:7,?j=1:7]
    end
    I???=?public_preprocess(batch)
    #?Evaluate?the?convolution
    weights?=?model.layers[1].weight
    conv_weights?=?reverse(reverse(weights,?dims=1),?dims=2)
    conved?=?[sum(I??[i,j]*conv_weights[i,j,1,channel]?for?i=1:7,?j=1:7)?for?channel?=?1:4]
    conved?=?map(((x,b),)->x?.+?b,?zip(conved,?model.layers[1].bias))
    


    這樣的實(shí)現(xiàn)(對(duì)維度重新排序的模)給出了相同的答案,但是用了這樣的操作:


    model*.*layers[*1*](batch)
    


    加入加密操作后,我們得到:


    I???=?public_preprocess(batch)
    C_I???=?map(I??)?do?Iij
        plain?=?CKKSEncoding{Tscale}(zero(plaintext_space(ckks_params)))
        plain?.=?OffsetArray(vec(Iij),?0:(N÷2-1))
        encrypt(kp,?plain)
    end
    weights?=?model.layers[1].weight
    conv_weights?=?reverse(reverse(weights,?dims=1),?dims=2)
    conved3?=?[sum(C_I??[i,j]*conv_weights[i,j,1,channel]?for?i=1:7,?j=1:7)?for?channel?=?1:4]
    conved2?=?map(((x,b),)->x?.+?b,?zip(conved3,?model.layers[1].bias))
    conved1?=?map(ToyFHE.modswitch,?conved2)
    


    注意,由于權(quán)重是公開的,所以不需要密鑰轉(zhuǎn)換,因此沒有擴(kuò)展密文的長(zhǎng)度。


    矩陣乘法


    接下來看看矩陣乘法是如何實(shí)現(xiàn)的。我們利用這樣的事實(shí)——可以旋轉(zhuǎn)向量中的元素,來重排序乘法索引。特別是,要考慮向量中矩陣元素的行優(yōu)先排序。然后,如果以行大小的倍數(shù)移動(dòng)向量,就可以得到列旋轉(zhuǎn)的效果,這可以提供充足的原語來實(shí)現(xiàn)矩陣乘法(至少是方陣)。我們不妨試一下:


    function?matmul_square_reordered(weights,?x)
        sum(1:size(weights,?1))?do?k
            #?We?rotate?the?columns?of?the?LHS?and?take?the?diagonal
            weight_diag?=?diag(circshift(weights,?(0,(k-1))))
            #?We?rotate?the?rows?of?the?RHS
            x_rotated?=?circshift(x,?(k-1,0))
            #?We?do?an?elementwise,?broadcast?multiply
            weight_diag?.*?x_rotated
    end
    end
    function?matmul_reorderd(weights,?x)
        sum(partition(1:256,?64))?do?range
            matmul_square_reordered(weights[:,?range],?x[range,?:])
        end
    end
    fc1_weights?=?model.layers[3].W
    x?=?rand(Float64,?256,?64)
    @assert?(fc1_weights*x)?≈?matmul_reorderd(fc1_weights,?x)
    


    當(dāng)然,對(duì)于一般的矩陣乘法,我們可能需要更好的方法,但是在本例中,現(xiàn)在這種程度就已經(jīng)足夠了。


    優(yōu)化代碼


    至此,我們?cè)O(shè)法將所有內(nèi)容整合在一起,而且也確實(shí)奏效了。這里提供了代碼作為參考(省略了參數(shù)選擇等設(shè)置):


    ek?=?keygen(EvalMultKey,?kp.priv)
    gk?=?keygen(GaloisKey,?kp.priv;?steps=64)
    I???=?public_preprocess(batch)
    C_I???=?map(I??)?do?Iij
        plain?=?CKKSEncoding{Tscale}(zero(plaintext_space(ckks_params)))
        plain?.=?OffsetArray(vec(Iij),?0:(N÷2-1))
        encrypt(kp,?plain)
    end
    weights?=?model.layers[1].weight
    conv_weights?=?reverse(reverse(weights,?dims=1),?dims=2)
    conved3?=?[sum(C_I??[i,j]*conv_weights[i,j,1,channel]?for?i=1:7,?j=1:7)?for?channel?=?1:4]
    conved2?=?map(((x,b),)->x?.+?b,?zip(conved3,?model.layers[1].bias))
    conved1?=?map(ToyFHE.modswitch,?conved2)
    Csqed1?=?map(x->x*x,?conved1)
    Csqed1?=?map(x->keyswitch(ek,?x),?Csqed1)
    Csqed1?=?map(ToyFHE.modswitch,?Csqed1)
    function?encrypted_matmul(gk,?weights,?x::ToyFHE.CipherText)
        result?=?repeat(diag(weights),?inner=64).*x
        rotated?=?x
        for?k?=?2:64
            rotated?=?ToyFHE.rotate(gk,?rotated)
            result?+=?repeat(diag(circshift(weights,?(0,(k-1)))),?inner=64)?.*?rotated
        end
        result
    end
    fq1_weights?=?model.layers[3].W
    Cfq1?=?sum(enumerate(partition(1:256,?64)))?do?(i,range)
        encrypted_matmul(gk,?fq1_weights[:,?range],?Csqed1[i])
    end
    Cfq1?=?Cfq1?.+?OffsetArray(repeat(model.layers[3].b,?inner=64),?0:4095)
    Cfq1?=?modswitch(Cfq1)
    Csqed2?=?Cfq1*Cfq1
    Csqed2?=?keyswitch(ek,?Csqed2)
    Csqed2?=?modswitch(Csqed2)
    function?naive_rectangular_matmul(gk,?weights,?x)
        @assert?size(weights,?1)?<?size(weights,?2)
        weights?=?vcat(weights,?zeros(eltype(weights),?size(weights,?2)-size(weights,?1),?size(weights,?2)))
        encrypted_matmul(gk,?weights,?x)
    end
    fq2_weights?=?model.layers[4].W
    Cresult?=?naive_rectangular_matmul(gk,?fq2_weights,?Csqed2)Cresult?=?Cresult?.+?OffsetArray(repeat(vcat(model.layers[4].b,?
    zeros(54)),?inner=64),?0:4095)
    


    雖然代碼看起來不是很清晰,但是如果你已經(jīng)進(jìn)行到這一步了,那你就應(yīng)該理解這個(gè)流程中的每一步。


    現(xiàn)在,把注意力轉(zhuǎn)移到可以讓這一切更好理解的抽象上。我們先跳出密碼學(xué)和機(jī)器學(xué)習(xí)領(lǐng)域,考慮編程語言設(shè)計(jì)的問題。Julia 可以實(shí)現(xiàn)強(qiáng)大的抽象,我們可以利用這一點(diǎn)構(gòu)建一些抽象。例如,可以將整個(gè)卷積提取過程封裝為自定義數(shù)組類型:


    using?BlockArrays
    """
    ????ExplodedConvArray{T,?Dims,?Storage}?<:?AbstractArray{T,?4}
    Represents?a?an?`nxmx1xb`?array?of?images,?but?rearranged?into?a
    series?of?convolution?windows.?Evaluating?a?convolution?compatible
    with?`Dims`?on?this?array?is?achievable?through?a?sequence?of
    scalar?multiplications?and?sums?on?the?underling?storage.
    """
    struct?ExplodedConvArray{T,?Dims,?Storage}?<:?AbstractArray{T,?4}
        #?sx*sy?matrix?of?b*(dx*dy)?matrices?of?extracted?elements
        #?where?(sx,?sy)?=?kernel_size(Dims)
        #???????(dx,?dy)=output_size(DenseConvDims(...))
        cdims::Dims
        x::Matrix{Storage}
        function?ExplodedConvArray{T,?Dims,?Storage}(cdims::Dims,?storage::Matrix{Storage})?where?{T,?Dims,?Storage}
    ???     @assert?all(==(size(storage[1])),?size.(storage))
    ???     new{T,?Dims,?Storage}(cdims,?storage)
        end
    end
    Base.size(ex::ExplodedConvArray)?=?(NNlib.input_size(ex.cdims)...,?1,?size(ex.x[1],?1))
    function?ExplodedConvArray{T}(cdims,?batch::AbstractArray{T,?4})?where?{T}
        x,?y?=?NNlib.output_size(cdims)
        kx,?ky?=?NNlib.kernel_size(cdims)
        stridex,?stridey?=?NNlib.stride(cdims)
        kax?=?OffsetArray(0:x-1,?0:x-1)
        kay?=?OffsetArray(0:x-1,?0:x-1)
        I?=?[[batch[i′*stridex?.+?(1:kx),?j′*stridey?.+?(1:ky),?1,?k]?for?i′=kax,?j′=kay]?for?k?=?1:size(batch,?4)]
        I???=?[[I[k][l...][i,j]?for?k=1:size(batch,?4),?l=product(kax,?kay)]?for?(i,j)?in?product(1:kx,?1:ky)]    ExplodedConvArray{T,?typeof(cdims),?eltype(I??)}(cdims,?I??)
    end
    function?NNlib.conv(x::ExplodedConvArray{<:Any,?Dims},?
    weights::AbstractArray{<:Any,?4},?cdims::Dims)?where?{Dims<:ConvDims}
        blocks?=?reshape([?
    Base.ReshapedArray(sum(x.x[i,j]*weights[i,j,1,channel]?for?i=1:7,?j=1:7),?(NNlib.output_size(cdims)...,1,size(x,?4)),?())?for?channel?=?1:4?],(1,1,4,1))
        BlockArrays._BlockArray(blocks,?BlockArrays.BlockSizes([8],?[8],?[1,1,1,1],?[64]))
    end
    


    注意,如原始代碼所示,這里用 BlockArrays 將 8*8*4*64 的數(shù)組表示成 4 個(gè) 8*8*1*64 的數(shù)組。所以現(xiàn)在,我們已經(jīng)得到了第一個(gè)步驟更好的表征(至少是在未加密數(shù)組上):


    julia>?cdims?=?DenseConvDims(batch,?model.layers[1].weight;?stride=(3,3),?padding=(0,0,0,0),?dilation=(1,1))
    DenseConvDims:?(28,?28,?1)?*?(7,?7)?->?(8,?8,?4),?stride:?(3,?3)?pad:?(0,?0,?0,?0),?dil:?(1,?1),?flip:?false
    julia>?a?=?ExplodedConvArray{eltype(batch)}(cdims,?batch);
    julia>?model(a)
    10×64?Array{Float32,2}:
    [snip]如何將這種表征帶入加密的世界呢?我們需要做兩件事:

    如何將這種表征帶入加密的世界呢?我們需要做兩件事:


    1. 我們想以這樣的方式加密結(jié)構(gòu)體(ExplodedConvArray),以致于對(duì)每個(gè)字段(field)都能得到一個(gè)密文。然后,通過查詢?cè)摵瘮?shù)在原始結(jié)構(gòu)上執(zhí)行的操作,在加密的結(jié)構(gòu)體上進(jìn)行運(yùn)算,并直接進(jìn)行相同的同態(tài)操作。

    2. 我們希望攔截某些在加密的上下文中以不同方式執(zhí)行的操作。


    幸運(yùn)的是 Julia 提供了可以同時(shí)執(zhí)行這兩個(gè)操作的抽象:使用 Cassette.jl 機(jī)制的編譯器插件。它是如何起作用的,以及如何使用它,都有些復(fù)雜,本文中不再深入介紹這部分內(nèi)容。簡(jiǎn)言之,你可以定義上下文(即「Excrypted」,然后定義在這樣的上下文中,運(yùn)算是如何起作用的規(guī)則)。例如,第二個(gè)要求可以寫成:


    所有這一切的最終結(jié)果是,用戶可以以最少的手工工作,寫完整個(gè)內(nèi)容:


    當(dāng)然,就算經(jīng)過了以上處理,代碼也不是最優(yōu)的。加密系統(tǒng)的參數(shù)(例如 ? 環(huán),什么時(shí)候模轉(zhuǎn)換,什么時(shí)候密鑰轉(zhuǎn)換等)表現(xiàn)出了在答案的準(zhǔn)確性、安全性以及性能之間的取舍,而且參數(shù)很大程度上取決于正在運(yùn)行的代碼。一般來說,人們希望編譯器能分析將要運(yùn)行的加密代碼,為給定的安全等級(jí)和所需精度提出參數(shù)建議,然后用戶以最少的人工操作來生成代碼。


    結(jié)語


    對(duì)于任何系統(tǒng)來說,安全地自動(dòng)執(zhí)行任意計(jì)算都是一項(xiàng)艱巨的任務(wù),但 Julia 的元編程功能和友好的語法都讓它成為合適的開發(fā)平臺(tái)。RAMPARTS 系統(tǒng)已經(jīng)做了一些嘗試,將簡(jiǎn)單的 Julia 代碼編譯到 PALISADE FHE 庫(kù)中。「Julia Computing」正在與 RAMPARTS 背后的專家在 Verona 平臺(tái)上合作,最近已經(jīng)發(fā)布了下一代版本。在過去的一年中,同態(tài)加密系統(tǒng)的性能才達(dá)到能以實(shí)際可用的速度評(píng)估有趣計(jì)算的程度。一扇嶄新的大門就此打開。隨著算法、軟件和硬件的進(jìn)步,同態(tài)加密必然會(huì)成為保護(hù)數(shù)百萬用戶隱私的主流技術(shù)。


    RAMPARTS 論文:https://eprint.iacr.org/2019/988.pdf

    報(bào)告:https://www.youtube.com/watch?v=_KLlMg6jKQg


    如果你想更深入地了解這一切是如何工作的,我已經(jīng)試著確保了 ToyFHE 庫(kù)的可讀性。這里還有一些文檔,希望這些文檔能幫助你進(jìn)一步理解涉及到的密碼學(xué)內(nèi)容。當(dāng)然,還有很多工作要做。如果你對(duì)這類工作感興趣,或者有有趣的應(yīng)用程序,請(qǐng)隨時(shí)與我們聯(lián)系。


    ToyFHE 庫(kù):https://github.com/JuliaComputing/ToyFHE.jl其他參考文檔:https://juliacomputing.github.io/ToyFHE.jl/dev/man/background/


    原文鏈接:

    https://juliacomputing.com/blog/2019/11/22/encrypted-machine-learning.html

    1.我的世界不允許你的消失,不管結(jié)局是否完美.

    No matter the ending is perfect or not, you cannot disappear from my world.

    2.愛情是一個(gè)精心設(shè)計(jì)的謊言

    Love is a carefully designed lie.

    3.承諾常常很像蝴蝶,美麗的飛盤旋然后不見

    Promises are often like the butterfly, which disappear after beautiful hover.

    4.凋謝是真實(shí)的 盛開只是一種過去

    Fading is true while flowering is past

    5.為什么幸福總是擦肩而過,偶爾想你的時(shí)候….就讓….回憶來陪我.

    Why I have never catched the happiness? Whenever I want you ,I will be accompanyed by the memory of...

    6.愛情…在指縫間承諾 指縫….在愛情下交纏.

    Love ,promised between the fingers

    Finger rift,twisted in the love

    7.如果你為著錯(cuò)過夕陽而哭泣,那么你就要錯(cuò)群星了

    If you weeped for the missing sunset,you would miss all the shining stars

    8.感受夢(mèng)的火焰,感覺飛舞瞬間,當(dāng)一切浪漫遙遠(yuǎn),永恒依然

    to feel the flame of dreaming and to feel the moment of dancing,when all the romance is far away,the eternity is always there

    9.茹菓只遈遇見,吥能停畱,吥茹吥遇見.

    If we can only encounter each other rather than stay with each other,then I wish we had never encountered .

    10.寧愿笑著流淚,嘢不哭著說后悔 心碎了,還需再補(bǔ)嗎?

    I would like weeping with the smile rather than repenting with the cry,when my heart is broken ,is it needed to fix?

    11.天空沒有翅膀的痕跡,而鳥兒已飛過

    There are no trails of the wings in the sky, while the birds has flied away.

    12.與你保持著一種暖昧的關(guān)系,怕自己會(huì)愛上你,怕你離開后,我會(huì)流淚

    When keeping the ambiguity with you ,I fear I will fall in love with you, and I fear I will cry after your leaving.

    13.人活著總是要得罪一些人的 就要看那些人是否值得得罪

    When alive ,we may probably offend some people.However, we must think about whether they are deserved offended.

    14.誰撿走了我的玻璃鞋,尋找遺失的玻璃鞋.

    I am looking for the missing glass-shoes who has picked it up

    15.命里哊時(shí)鐘需哊 命里無時(shí)莫強(qiáng)求

    You will have it if it belongs to you,whereas you don't kveth for it if it doesn't appear in your life.

    16.沒有誰對(duì)不起誰,只有誰不懂得珍惜誰.

    No one indebted for others,while many people don't know how to cherish others.

    17.永遠(yuǎn)不是一種距離,而是一種決定。

    Eternity is not a distance but a decision.

    18.在回憶里繼續(xù)夢(mèng)幻不如在地獄里等待天堂

    Dreaming in the memory is not as good as waiting for the paradise in the hell

    19.哪里有真愛存在,哪里就有奇跡。

    Where there is great love, there are always miracles.

    20、愛情就像一只蝴蝶,它喜歡飛到哪里,就把歡樂帶到哪里。

    Love is like a butterfly. It goes where it pleases and it pleases where it goes.

    21.假如每次想起你我都會(huì)得到一朵鮮花,那么我將永遠(yuǎn)在花叢中徜徉。

    If I had a single flower for every time I think about you, I could walk forever in my garden.

    22.有了你,我迷失了自我。失去你,我多么希望自己再度迷失。

    Within you I lose myself, without you I find myself wanting to be lost again.

    23.每一個(gè)沐浴在愛河中的人都是詩(shī)人。

    At the touch of love everyone becomes a poet.

    24.Look into my eyes - you will see what you mean to me.

    看看我的眼睛,你會(huì)發(fā)現(xiàn)你對(duì)我而言意味著什么。

    25.Distance makes the hearts grow fonder.

    距離使兩顆心靠得更近。

    26.I need him like I need the air to breathe.

    我需要他,正如我需要呼吸空氣。

    27.If equal affection cannot be, let the more loving be me.

    如果沒有相等的愛,那就讓我愛多一些吧。

    28.Love is a vine that grows into our hearts.

    愛是長(zhǎng)在我們心里的藤蔓。

    29.If I know what love is, it is because of you.

    因?yàn)槟悖叶昧藧邸?/p>

    30.Love is the greatest refreshment in life.

    愛情是生活最好的提神劑。

    31.Love never dies.

    愛情永不死。

    32.The darkness is no darkness with thee.

    有了你,黑暗不再是黑暗。

    33.We cease loving ourselves if no one loves us.

    如果沒有人愛我們,我們也就不會(huì)再愛自己了。

    34.There is no remedy for love but to love more.

    治療愛的創(chuàng)傷唯有加倍地去愛。

    35.When love is not madness, it is not love.

    如果愛不瘋狂就不是愛了。

    36.A heart that loves is always young.

    有愛的心永遠(yuǎn)年輕。

    37.Love is blind.

    愛情是盲目的。

    38.Love is like the moon, when it does not increase, it decreases.

    愛情就像月亮,不增則減。

    39.The soul cannot live without love.

    靈魂不能沒有愛而存在。

    40.Brief is life, but love is long.

    生命雖短,愛卻綿長(zhǎng)。

    41.Who travels for love finds a thousand miles not longer than one.

    在愛人眼里,一千里的旅程不過一里。

    42.Love keeps the cold out better than a cloak.

    愛比大衣更能驅(qū)走寒冷。

    43.Take away love, and our earth is a tomb.

    沒有了愛,地球便成了墳?zāi)埂?/p>

    44.My heart is with you.

    我的愛與你同在。

    45.I miss you so much already and I haven't even left yet!

    盡管還不曾離開,我已對(duì)你朝思暮想!

    46.I'll think of you every step of the way.

    我會(huì)想你,在漫漫長(zhǎng)路的每一步。

    47.Wherever you go, whatever you do, I will be right here waiting for you. 無論你身在何處,無論你為何忙碌,我都會(huì)在此守候。

    48.Passionate love is a quenchless thirst.

    熱烈的愛情是不可抑制的渴望。

    49.The most precious possession that ever comes to a man in this world is a woman's heart.

    在這個(gè)世界上,男人最珍貴的財(cái)產(chǎn)就是一個(gè)女人的心。

    50.One word frees us of all the weight and pain in life.That word is love. 有一個(gè)詞可以讓我們擺脫生活中所有的負(fù)擔(dān)和痛苦,那就是"愛情"。

    51.Every day without you is like a book without pages.

    沒有你的日子就像一本沒有書頁的書。

    52.Love is hard to get into, but harder to get out of.

    愛很難投入,但一旦投入,便更難走出。

    53.Love is a light that never dims.

    愛是一盞永不昏暗的明燈。

    54.May your love soar on the wings of a dove in flight.

    愿你的愛乘著飛翔的白鴿,展翅高飛。

    55.She who has never loved, has never lived.

    人活著總要愛一回。

    56.Life is the flower for which love is the honey.

    生命如花,愛情是蜜。

    57.No words are necessary between two loving hearts.

    兩顆相愛的心之間不需要言語。

    58.Precious things are very few in this world. That is the reason there is just one you.

    在這世上珍貴的東西總是罕有,所以這世上只有一個(gè)你。

    59.You make my heart smile.

    我的心因你而笑。

    60.The road to a lover's house is never long.

    通往愛人家里的路總不會(huì)漫長(zhǎng)。

    61.Why do the good girls, always want the bad boys?

    為何好女孩總喜歡壞男孩?

    62.Being with you is like walking on a very clear morning.

    和你在一起就像在一個(gè)清爽的早晨漫步。

    63.It is never too late to fall in love.

    愛永遠(yuǎn)不會(huì)嫌晚。

    64.To the world you may be just one person. To the person you may be the world.

    對(duì)于世界,你可能只是一個(gè)人,但對(duì)于某個(gè)人,你卻是整個(gè)世界。

    65.Where there is love, there are always wishes.

    哪里有愛,哪里就有希望。

    66.You don't love a woman because she is beautiful, but she is beautiful because you love her.

    你不會(huì)因?yàn)槊利惾垡粋€(gè)女人,但她卻會(huì)因?yàn)槟愕膼鄱兊妹利悺?/p>

    67.Love is something eternal; the aspect may change, but not the essence.

    愛是永恒的,外表可能改變,但本質(zhì)永遠(yuǎn)不變。

    68.Love is not a matter of counting the days. It's making the days count.

    愛情不是數(shù)著日子過去,它讓每個(gè)日子都變得有意義。

    69.With the wonder of your love, the sun above always shines.

    擁有你美麗的愛情,太陽就永遠(yuǎn)明媚。

    70.Love is a fabric that nature wove and fantasy embroidered.

    愛情是一方織巾,用自然編織,用幻想點(diǎn)綴。

    71.First love is unforgettable all one's life.

    初戀是永生難忘的。

    72.In the very smallest cot there is room enough for a loving pair.

    哪怕是最小的茅舍,對(duì)一對(duì)戀人來說都有足夠的空間。

    73.Love without end hath no end.

    情綿綿,愛無邊。

    74.Love's tongue is in the eyes.

    愛情的話語全在雙眼之中。

    75.In love folly is always sweet.

    戀愛中,干傻事總是讓人感到十分美妙。

    76.There is no hiding from lover's eyes.

    什么也瞞不過戀人的眼睛。

    77.The only present love demands is love.

    愛所祈求的唯一禮物就是愛。

    78.The heart that once truly loves never forgets.

    真摯戀愛過的心永不忘卻。

    79.Love warms more than a thousand fires.

    愛情的熾熱勝過千萬團(tuán)的火。

    80.Your smiling at me is my daily dose of magic.

    你嫣然的微笑是我每日享受到的魅力。

    81.Your kiss still burns on my lips, everyday of mine is so beautiful.

    你的吻還在我的唇上發(fā)燙,從此我的日子變得如此美麗。

    82.Love understands love; it needs no talk.

    相愛的心息息相通,無需用言語傾訴。

    83.Love me little and love me long.

    不求情意綿綿,但求天長(zhǎng)地久。

    84.First impression of you is most lasting.

    對(duì)你最初的印象,久久難以忘懷。

    85.When the words "I love you" were said by you for the first time, my world blossoms.

    第一次聽到你對(duì)我說"我愛你",我的世界一瞬間鮮花綻開。

    86.Tell me you are mine. I'll be yours through all the years, till the end of time.

    請(qǐng)告訴我你是我的。歲歲年年,我都屬于你,永遠(yuǎn)永遠(yuǎn)。

    87.Love is a fire which burns unseen.

    愛情是無形燃燒的火焰。

    88.I feel happy at times we have had angry words but these have been kissed away.

    我們生氣爭(zhēng)執(zhí)時(shí),愛的雙唇把它們吻得無影無蹤,我的心也頓覺甜蜜。

    89.You cannot appreciate happiness unless you have known sadness too.

    不知道什么是憂傷,就不會(huì)真正感激幸福。

    90.But if the while I think on thee, dear friend, all losses are restored, and sorrows end.

    只要我一想起你,親愛的人,所有的失落和遺憾煙消云散

    =================================

    想學(xué)接地氣的實(shí)用英語表達(dá)?想看最新環(huán)球熱點(diǎn)資訊?想聽最原汁原味的英(美)音?

    關(guān)注微信訂閱號(hào)洛基英語:rocky_english(長(zhǎng)按微信號(hào)復(fù)制關(guān)注)

    每周七天,我們?yōu)槟挠⒄Z保鮮!

網(wǎng)站首頁   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號(hào):冀ICP備2024067069號(hào)-3 北京科技有限公司版權(quán)所有