加密模型推理:在加密数据上运行模型的方案
FreeGuideOnline
最新
2026-06-29
bash pip install pyfhel==3.4.3 torch numpy
### 2.2 生成密钥与上下文
```python
from Pyfhel import Pyfhel
HE = Pyfhel()
ckks_params = {
'scheme': 'CKKS', # 支持浮点近似
'n': 2**14, # 多项式模数多项数
'scale': 2**30, # 缩放因子
'qi_sizes': [60, 30, 60] # 模数链
}
HE.contextGen(**ckks_params)
HE.keyGen()
HE.relinKeyGen() # 重线性化密钥,乘法后需要
HE.rotateKeyGen() # 用于向量化旋转
2.3 模型准备与参数提取
我们假设一个极简的两层全连接网络(明文训练好):
import torch
import torch.nn as nn
class TinyModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(8, 4)
self.fc2 = nn.Linear(4, 2)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
model = TinyModel()
# 假设已有权重
weight1 = model.fc1.weight.data.numpy()
bias1 = model.fc1.bias.data.numpy()
weight2 = model.fc2.weight.data.numpy()
bias2 = model.fc2.bias.data.numpy()
2.4 数据加密(客户端)
用户将自己的数据加密:
# 假设输入是一个8维向量
input_data = np.array([0.5, -0.2, 1.1, -0.9, 0.3, 0.7, -1.2, 0.0])
# 编码并加密为CKKS密文(需要批量编码,这里简化为单样本)
# 实际中通常将多个样本打包到同一个密文中以提高效率(batch axis)
ct_input = HE.encodeFrac(input_data) # 先编码为多项式
ct_input = HE.encryptFrac(ct_input) # 再加密得到密文对象
2.5 同态线性层运算
服务器持有模型权重(明文),直接在密文上执行矩阵乘法+偏置。CKKS支持密文*明文,以及密文加法。
# 第一层:Y = X·W + b
def encrypted_linear(ct_x, weight, bias, HE):
# 权重每一行与密文向量做内积,需要先转置处理
output_cts = []
for i in range(weight.shape[0]): # 输出维度
# 密文乘以明文向量:通过逐元素乘法然后求和
row_sum = None
for j in range(weight.shape[1]):
# 将权重[i,j]作为明文标量,与密文第j个元素相乘
# Pyfhel中,密文乘明文标量:HE.multiply_plain(ct, pt)
pt_val = weight[i,j]
term = HE.multiply_plain(ct_x[j], pt_val) # ct_x是密文向量的第j个槽位提取?
# 注意:以上为概念演示,实际需要使用槽位旋转与向量化。
if row_sum is None:
row_sum = term
else:
row_sum += term
# 加上偏置
row_sum += bias[i]
output_cts.append(row_sum)
return output_cts # 此时每个元素仍为密文
说明:真正的实现需利用CKKS的SIMD特性,通过旋转不同槽位高效完成向量内积。为了清晰,这里简化了操作。
2.6 非线性激活的近似
ReLU函数在FHE中不能直接计算,需要用多项式近似,如 f(x) = 0.5x + 0.25x^2(简单低阶)或更精确的 0.15625x^2 + 0.5x + 0.28125(二阶)以适应定义域。
def poly_relu(ct, HE):
# 平方
ct_sq = HE.multiply(ct, ct) # 密文平方
HE.relinearize(ct_sq) # 重线性化减小密文规模
# 多项式: 0.25*x^2 + 0.5*x
ct_025 = HE.multiply_plain(ct_sq, 0.25)
ct_05 = HE.multiply_plain(ct, 0.5)
ct_res = ct_025 + ct_05
return ct_res
2.7 结果解密
将最后层的密文返回客户端:
# 客户端解密
result_ct = final_ct[0] # 假设单输出
result_pt = HE.decryptFrac(result_ct)[:2] # 解码前2个槽位
print("解密预测结果:", result_pt)