python
import numpy as np
def conv2d_naive(image, kernel, stride=1):
H, W = image.shape
kh, kw = kernel.shape
out_h = (H - kh) // stride + 1
out_w = (W - kw) // stride + 1
output = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
r, c = i * stride, j * stride
patch = image[r:r+kh, c:c+kw]
output[i, j] = np.sum(patch * kernel) # elementwise multiply + sum
return output
image = np.random.randn(8, 8)
edge_kernel = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]]) # vertical edge detector
out = conv2d_naive(image, edge_kernel, stride=1)
print(out.shape) # (6, 6)Every output pixel is a dot product between the filter and a local patch — exactly the same operation as a fully-connected layer, just applied to a small local window and reused everywhere. Real implementations use im2col or FFT-based tricks and run on GPU tensor cores, but this loop is mathematically identical.