34、卷积实战 - 手写一个基础卷积算法

本文介绍了如何手动实现一个基础卷积算法,通过Python代码示例,详细讲解了卷积运算的过程。从卷积核在图像上的滑动,到涉及的循环次数,解释了卷积运算的复杂性。此外,提供了基础版本的Python代码,并建议读者通过反复实践来深入理解。文章还提及卷积算法在面试中的重要性,以及后续的优化讨论。

前面基本上把卷积这一算法的原理和公式介绍完了,如果还有不懂的,可以多翻几遍前面的章节内容,深入理解一下。

本节加一个实战,大家可以手动来实现一个卷积算法,本文中以 python 代码为例,C++ 的代码可以查看本节后面的链接。

说到卷积实现,其实就是自己手写一个卷积算子。很多做人工智能的公司都会招算子开发工程师,其中手写一些算子的实现属于基础能力,而且是入门门槛。

相对应的,进阶能力是可以在很多硬件平台上使用不同的指令集来完成一个算子的实现,常见的如GPU上实现,并且对手写的算法进行优化。

因为我们大家都有笔记本,并且也都有 Intel 的 CPU 可以用,因此我们在 intel 的 CPU 的来实现这个卷积算法,就像我们平常写python和C++一样,此时的指令集为最常见的 x86 架构指令集。

需要说明的是,在小册后面的实战部分以及性能调优部分,我们也会基于大众电脑中的x86 CPU指令集来进行,并且会使用 avx2 的向量指令集做优化。

但是,不论是在哪个平台上,用哪种指令集完成代码编写,思路都是类似的,只不过你可能用的是GPU的指令,而我用的是CPU的指令。

卷积的python实现

d7736af485be4c54ba81a16d4dc0d124~noop.image

要实现算法,第一步先看看图,照着图来实现

### 卷积算法手写实现 卷积操作是深度学习中的核心组成部分,尤其在图像处理领域中被广泛应用。虽然现有的深度学习框架(如 TensorFlow 和 PyTorch)提供了高效的卷积实现,但在某些特定场景下,手写卷积代码可能更具有灵活性和学习价值。 以下是一个使用 Python 实现的简单卷积函数示例,该函数支持填充(padding)和步长(stride)参数,并且能够处理多通道输入与多输出通道的情况。 ```python import numpy as np def conv_naive(x, out_channels, kernel_size, padding=0, stride=1): # x 的形状为 [batch_size, height, width, in_channels] batch_size, in_height, in_width, in_channels = x.shape # 初始化卷积核,形状为 [kernel_size, kernel_size, in_channels, out_channels] kernel = np.random.rand(kernel_size, kernel_size, in_channels, out_channels) # 如果有填充,则对输入进行零填充 if padding > 0: pad_x = np.zeros((batch_size, in_height + 2 * padding, in_width + 2 * padding, in_channels)) pad_x[:, padding:-padding, padding:-padding, :] = x else: pad_x = x # 计算输出特征图的高度和宽度 out_height = (in_height + 2 * padding - kernel_size) // stride + 1 out_width = (in_width + 2 * padding - kernel_size) // stride + 1 # 初始化输出张量 out = np.zeros((batch_size, out_height, out_width, out_channels)) # 遍历输出特征图的每个位置 for i in range(out_height): for j in range(out_width): # 提取当前区域的输入块 roi_x = pad_x[:, i * stride:i * stride + kernel_size, j * stride:j * stride + kernel_size, :] # 对输入块与卷积核进行逐元素相乘并求和,得到输出值 # 将 roi_x 扩展一个维度以匹配 kernel 的形状 # 然后进行广播相乘,并对空间维度求和 conv_result = np.tile(np.expand_dims(roi_x, -1), (1, 1, 1, 1, out_channels)) * kernel out[:, i, j, :] = np.squeeze(np.sum(conv_result, axis=(1, 2, 3)), axis=3) return out ``` #### 使用示例 为了验证上述代码的功能,我们可以构造一个简单的输入张量并调用 `conv_naive` 函数: ```python if __name__ == '__main__': # 构造一个随机输入张量,形状为 [batch_size=1, height=10, width=10, channels=3] x = np.random.rand(1, 10, 10, 3) # 调用手写卷积函数,设置输出通道数为15,卷积核大小为3x3,填充为1,步长为2 output = conv_naive(x, out_channels=15, kernel_size=3, padding=1, stride=2) # 输出结果的形状应为 [1, 5, 5, 15] print(output.shape) # 应该打印 (1, 5, 5, 15) ``` 此代码通过手动遍历输入张量的每个局部区域,并将其与卷积核进行点乘,最后将结果累加来计算输出。尽管这种方法在性能上不如优化过的库实现,但它非常适合用于教学目的或理解卷积的工作原理。 #### 性能优化建议 - **向量化操作**:尽可能减少循环嵌套,利用 NumPy 的广播机制和矩阵运算来加速计算。 - **内存访问优化**:确保数据在内存中的布局有利于缓存行的使用,例如 NHWC 格式更适合现代 CPU 的内存访问模式。 - **并行化**:可以考虑使用多线程或多进程来并行处理不同的样本或特征图部分。 以上实现展示了如何从头开始构建一个基础卷积层[^3]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董董灿是个攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值