同态加密和SEAL库的介绍(十)CKKS 参数心得 2

写在前面:

本篇继续上篇的测试,首先针对密文深度乘法情况 ,虽然密文乘法本就是应该尽量避免的(时间和内存成本过高),更不用说深度乘法了,但是为了测试的完整性,还是做一下方便大家比对。

其次是关于参数设置对内存占用的影响,这个十分重要,因为我们在跑模型的时候,经常进程被 kill,因为确实是密文出乎意料的大,后面根据测试数据大家就能看出来。

一、测试配置

因为和之前的设置一样,这里就不多介绍了,直接放代码。

1.1 前置设置

cpp 复制代码
EncryptionParameters parms(scheme_type::ckks);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 50, 30, 30, 50 }));
double scale = pow(2.0, 30);
SEALContext context(parms);

KeyGenerator keygen(context);
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
GaloisKeys gal_keys;
keygen.create_galois_keys(gal_keys);
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
CKKSEncoder encoder(context);
size_t slot_count = encoder.slot_count();

1.2 输入设置

为了具有对比参考价值,这里输入也设置成一样,不过对三个乘数进行加密。

cpp 复制代码
vector<double> the_input;
the_input.reserve(slot_count);
for (size_t i = 0; i < slot_count; i++){
    the_input.push_back((double)i);
}
Plaintext the_input_plain;
encoder.encode(the_input, scale, the_input_plain);
Ciphertext the_input_enc;
encryptor.encrypt(the_input_plain, the_input_enc);

Plaintext const_plain_1, const_plain_2, const_plain_3;
encoder.encode(3.14, scale, const_plain_1);
encoder.encode(3.14, scale, const_plain_2);
encoder.encode(3.14, scale, const_plain_3);
Ciphertext const_cipher_1, const_cipher_2, const_cipher_3;
encryptor.encrypt(const_plain_1, const_cipher_1);
encryptor.encrypt(const_plain_2, const_cipher_2);
encryptor.encrypt(const_plain_3, const_cipher_3);

二、密文乘法测试

跟上篇一样,每乘一次进行解密输出,便于查看中间结果信息。基于之前理论,继续 三次乘法,两次 Rescale。

2.1 乘法设置及密文大小观察

先写一下乘法代码:(注意所用的乘法函数和之前不同

cpp 复制代码
evaluator.multiply_inplace(the_input_enc, const_cipher_1);
evaluator.rescale_to_next_inplace(the_input_enc);

evaluator.mod_switch_to_inplace(const_cipher_2, the_input_enc.parms_id());
const_cipher_2.scale() = the_input_enc.scale();
evaluator.multiply_inplace(the_input_enc, const_cipher_2);

evaluator.rescale_to_next_inplace(the_input_enc);

evaluator.mod_switch_to_inplace(const_cipher_3, the_input_enc.parms_id());
const_cipher_3.scale() = the_input_enc.scale();
evaluator.multiply_inplace(the_input_enc, const_cipher_3);

decryptor.decrypt(the_input_enc, the_input_plain);
encoder.decode(the_input_plain, the_input);

先进行两次乘法查看中间结果:(这里输出一下 密文大小

对比之前的明文乘法,密文大小产生了变化,明文乘法后,大小不变 ;**这里密文相乘后,结果的密文大小达到了3;**第二次乘法后,大小变成了4;当然这里容量的变化比较明显,不过不知道其和大小的具体区别。


2.2 重新线性化观察

引入重新线性化,继续观察:

此处可以发现,每次重新线性化后,密文大小会减小到2,但是不会改变容量 。另外,最后一位的精确值是 40375.062 ,上面对第二次乘法和第二次重新线性化后都进行了解密,对比不进行重新线性化,精度并未明显增强。因为 CKKS 没有噪声预算的概念,所以重新线性化在此处,并未观察到除减小密文外,明显的其他增益效果。

三、深度 密文乘法测试

接下来,将 coeff_modulus 的长度为 4 和 5 分别进行测试。 (下面也进行了重新线性化)


3.1 长度为4的模数链

scale = 30,coeff_modulus = 160 (50 + 30 + 30 + 50) bits

果然,想进行第三次乘法,会报错:scale out of bounds!

按照上篇的理论,当处在模数链底层时,乘法结果的 scale 要小于 coeff_modulus 第一位


那更改参数为:scale = 29,coeff_modulus = 176 (59 + 29 + 29 + 59) bits

果然就不报错,成功进行了第三次密文乘法。但是,第二次乘法结果还近似正确,第三次结果就不正确了 (第一位本应该是0的),证明此种参数配置虽然可以乘,但是精度严重不足。

3.2 长度为 5 的模数链

既然上面的结论同上篇相同,那同理继续拉长模数链:(为了减少冗余,只截后面乘法结果)
scale = 30,coeff_modulus = 190 (50 + 30 + 30 + 30 + 50) bits

第三次乘法后,第三位的精确结果是:61.9183,最后一位应该是:126777.6947。可以看出,精度还可以。

另外,为了严谨,我还尝试了不进行重新线性化的三次密文乘法,结果为:

密文长度从一开始的2,增长到了5,但是解密的结果与上面近似。再次验证了重新线性化,并未带来精确度的提升。

3.3 深度乘法总结

本节的测试与上篇明文乘法的结论相同,即:

  1. 模数链限制了乘法深度(准确说是 Rescale 次数);
  2. 处在模数链底的时候较为特殊:注意设置 coeff_modulus 第一位大于乘后 scale;
  3. 要想提高精度,就要适当拉长模数链(但是代价更大,下面会测试)。

四、参数设置对内存占用的影响

先叠甲:

本节测试是统计不同参数设置,对内存占用的影响。不同性能的计算机测试数据可能有差异 ,且我是用 Debug 模式运行,监视内存占用得出的数据,内存本身包含了 "上下文环境、各种密钥和实例" ,甚至我每次运行都有波动,故不能当作精确值来推算,只具有相对意义


测试说明:(为了减少上述因素带来的差异,故编码和加密的数量设置的比较多)

设置模式为 CKKS方案,设置的 poly_modulus_degree = 8192,创建 二维 Plaintext 和 Ciphertext 数组。步骤如下:

  1. 创建 [50,50] 的 Plaintext 数组,即一共 2500 个明文块;
  2. 二层循环遍历数组,依次对每块进行编码(编码内容一样,都是相同的4096个数);
  3. 编码结束后,统计当前的 内存占用 和 程序运行时间;
  4. 创建 [50,50] 的 Ciphertext 数组,即一共 2500 个密文块;
  5. 二层循环遍历数组,依次对每块明文加密后放入对应密文块;
  6. 加密结束后,统计当前的 内存占用 和 程序运行时间。

这里测试的含义是:poly_modulus_degree = 8192 时,不同参数设置下,编码 2500个 明文块,以及加密 2500 个密文块。 对应能存储的数据数量为:


因为测试比较无聊,这里直接上结果:(再次强调,因为波动的原因,忽略小差异)

从表中发现结论:

  1. 明文块的大小 和 编码时间 相对友好,密文块大小 和 加密的运行时间 就很夸张了;
  2. 纵向来看,调大中间值 和 scale,几乎不会影响内存占用;
  3. 横向来看,加长模数链,会同时增加明文块和密文块的大小!

上节提到了,除了加大scale,加长模数链能提高结果精度,但是这里会加大内存。所以第三条结论比较重要,为此再追加测试:

证明,确实加长模数链会加大明文和密文的内存,相应的计算时长应该也会增长(因为 编码 和 加密 时间确实长了)。另外,本次虽然只做了 CKKS 方案,但是 coeff_modulus 的设置相同,故其他方案的结论应该也类似。

五、本篇总结

本篇继续上篇针对 CKKS 方案的测试,首先证明了 重新线性化 虽然会减少密文长度,但是并不会对 计算结果的精度 有明显的影响模数链的长度确实会限制乘法深度(具体来说是 Rescale 次数),这点上密文乘法和明文乘法相同

上篇中证明了 增加 scale 会提高精度,本篇也继续证明了 增长模数链 也能增加精度,但是内存测试后,前者 scale 不会提高内存,后者会增加 内存成本 和 时间成本。

总结会发现,在应用同态加密的时候,首先 乘法深度很是受限,其次 内存成本 和 时间成本都是很需要考量的。故在设计算法的时候,要综合考虑多方面因素,还是比较费工夫的。

相关推荐
小奏技术5 分钟前
我用github新开源的3D图生成工具生成了自己github历史贡献3D图
后端·开源
LabVIEW开发21 分钟前
什么样的LabVIEW控制算自动控制?
算法·labview
liuming199237 分钟前
Halcon中histo_2dim(Operator)算子原理及应用详解
图像处理·人工智能·深度学习·算法·机器学习·计算机视觉·视觉检测
sc写算法1 小时前
Hash 映射
数据结构·算法·哈希算法
雅妮yyn1 小时前
头歌数据结构-排序的实现及其应用
数据结构·算法
云边有个稻草人1 小时前
【优选算法】—移动零(双指针算法)
算法·排序算法·双指针算法
小墨&晓末1 小时前
【PythonGui实战】自动摇号小程序
python·算法·小程序·系统安全
落魄君子2 小时前
SVM分类-支持向量机(Support Vector Machine)
神经网络·算法·支持向量机·分类
上理考研周导师3 小时前
【单片机原理】第1章 微机基础知识,运算器,控制器,寄存器,微机工作过程,数制转换
算法