<jupyter_start><jupyter_text>Torch 2d convolution<jupyter_code>def torch_conv_2d(x, kernel, stride):
    return torch.nn.functional.conv2d(
        input=x, weight=kernel, stride=stride, padding=0, dilation=1
    )<jupyter_output><empty_output><jupyter_text>Generate input<jupyter_code># input image dimension n * n
x_size = 4
# kernel dimension n * n
k_size = 2
# stride
stride = 1

# generated incremeneted values: 1, 2, ..., n^2
x = np.arange(1, x_size ** 2 + 1).reshape(x_size, x_size)
kernel = np.arange(1, k_size ** 2 + 1).reshape(k_size, k_size)

# generated random values
# x = np.random.randn(x_size, x_size)
# kernel = np.random.randn(k_size, k_size)

print("input", x.shape)
print(x)
print("kernel", kernel.shape)
print(kernel)<jupyter_output>input (4, 4)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]
 [13 14 15 16]]
kernel (2, 2)
[[1 2]
 [3 4]]<jupyter_text>TenSEAL context<jupyter_code># Create TenSEAL context
context = ts.context(
    ts.SCHEME_TYPE.CKKS, 8192, coeff_mod_bit_sizes=[60, 40, 40, 60]
)
# set the scale
context.global_scale = pow(2, 40)
# generated galois keys in order to do rotation on ciphertext vectors
context.generate_galois_keys()<jupyter_output><empty_output><jupyter_text>For each convolution layer, a communication between the client and server is required. The server send the ciphertext (encrypted vector) to the client which is the input of the next convolution layer, in order to decrypt it and apply im2col (Image Block to Column) on the that input.After that the client encode and encrypt the input matrix in a vertical scan (column-major) and send it back to the server.<jupyter_code>%%time 

x_enc, windows_nb = ts.im2col_encoding(context, x, kernel.shape[0], kernel.shape[1], stride)

print("windows number: ", windows_nb)
print("ckksvector size: ", x_enc.size())

y_enc = x_enc.conv2d_im2col(kernel.tolist(), windows_nb)

print(y_enc.size())
y_plain = y_enc.decrypt()

print("y_enc")
print(y_plain)<jupyter_output>windows number:  9
ckksvector size:  36
9
y_enc
[44.00000566594027, 54.00000698409561, 64.00000865907336, 84.0000112607995, 94.0000125997811, 104.00001396028475, 124.00001662478137, 134.00001797146558, 144.00001931406726]
CPU times: user 25.7 ms, sys: 0 ns, total: 25.7 ms
Wall time: 24.6 ms<jupyter_text>Compare the result to torch conv2d<jupyter_code>y_torch = torch_conv_2d(
    torch.from_numpy(x.astype("float32")).unsqueeze(0).unsqueeze(0),
    torch.from_numpy(kernel.astype("float32")).unsqueeze(0).unsqueeze(0),
    stride
)
y_torch = y_torch.flatten().numpy()
print("y_toch")
print(y_torch)

assert almost_equal(y_plain, y_torch, 0)<jupyter_output>y_toch
[ 44.  54.  64.  84.  94. 104. 124. 134. 144.]
{'fraction_non_alphanumeric': 0.12415856394913986, 'fraction_numerical': 0.09798055347793568, 'mean_word_length': 5.308962264150943, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '319448', 'license': 'Apache-2.0', 'n_tokens_mistral': 1203, 'n_tokens_neox': 1025, 'n_words': 265, 'path': 'tutorials/experimental/Convolution.ipynb', 'repo_name': 'wonderit/TenSEAL', 'size': '2674'}