- 前言
卷积神经网络与全连接神经网络类似, 可以理解成一种变换, 这种变换一般由卷积、池化、激活函数等一系列操作组合而成. 本文就”卷积”部分稍作介绍.
- 卷积介绍
卷积可以看作是输入和卷积核之间的内积运算, 是两个实质函数之间的一种数学运算. 在卷积运算中, 通常使用卷积核将输入数据进行卷积运算得到的输出作为特征映射, 每个卷积核可获得一个特征映射.
如图所示, 一张大小为5 × 5 × 3 5 \times5 \times3 5 ×5 ×3的图片经过零填充后, 大小变为 7 × 7 × 3 7 \times 7 \times 3 7 ×7 ×3. 使用两个大小为3 × 3 × 3 3 \times 3 \times 3 3 ×3 ×3的卷积核进行步长为1 1 1的卷积运算, 最后得到一个大小为3 × 3 × 2 3 \times 3 \times 2 3 ×3 ×2的 feature map.
可以看到, 卷积核在图片所对应的矩阵中滑动. 每滑动到一个位置, 将对应数字相乘并求和, 得到一个特征图矩阵的元素.
注意, 卷积核每次滑动的步长为1 1 1, 才能滑动到矩阵的边缘部分.
; 1.1 卷积的三种模式:
-
FULL
橙色部分为image, 蓝色部分为filter. full模式的意思是, 从filter和image刚相交开始做卷积, 白色部分为填0. -
SAME
当filter的中心与image的边角重合时, 开始做卷积运算. -
VALID
当filter全部在image里面的时候,进行卷积运算.
1.2 feature map 公式计算
首先定义如下参数,
- 输入大小B × H × W × C B \times H\times W \times C B ×H ×W ×C
- 卷积核大小b × h × w × C b \times h \times w \times C b ×h ×w ×C
- 步长S S S
1.FULL:
输出大小 B × ⌊ H + h + 1 S ⌋ × ⌊ W + w + 1 S ⌋ × b B \times \lfloor \frac {H + h +1} S \rfloor \times \lfloor \frac {W + w +1} S \rfloor \times b B ×⌊S H +h +1 ⌋×⌊S W +w +1 ⌋×b
2.SAME:
输出大小 B × ⌊ H S ⌋ × ⌊ W S ⌋ × b B \times \lfloor \frac HS \rfloor \times \lfloor \frac WS \rfloor\times b B ×⌊S H ⌋×⌊S W ⌋×b
3.VALID:
输出大小 B × ⌊ H − h + 1 S ⌋ × ⌊ W − w + 1 S ⌋ × b B \times \lfloor \frac {H – h +1} S \rfloor \times \lfloor \frac {W – w +1}{S} \rfloor \times b B ×⌊S H −h +1 ⌋×⌊S W −w +1 ⌋×b
Tensorflow中的卷积有”same”和”valid”两种模式
Pytorch中可以直接通过设置参数”padding”来控制零层的填充.
同样, 我们可以基于零层填充的圈数P P P, 得到我们的另一个计算公式:
输出大小 B × ⌊ H − h + 2 P S + 1 ⌋ × ⌊ W − w + 2 P S + 1 ⌋ × b B \times \lfloor \frac{H-h+2P}{S} + 1 \rfloor \times \lfloor \frac{W-w+2P}{S} + 1 \rfloor \times b B ×⌊S H −h +2 P +1 ⌋×⌊S W −w +2 P +1 ⌋×b
- 代码实现
import numpy as np
import math
class Conv2D():
def __init__(self, inputShape, outputChannel, kernelSize, stride=1, method=""):
self.height = inputShape[1]
self.width = inputShape[2]
self.inputChannel = inputShape[-1]
self.outputChannel = outputChannel
self.batchSize = inputShape[0]
self.stride = stride
self.kernelSize = kernelSize
self.method = method
self.weights = np.random.standard_normal([self.inputChannel, kernelSize, kernelSize, self.outputChannel])
self.bias = np.random.standard_normal(self.outputChannel)
"""
# This part has some problems
if method == "FULL":
self.output = np.zeros(inputShape[0],
math.floor((inputShape[1] - kernelSize + 2 * (kernelSize - 1)) / self.stride) + 1,
math.floor((inputShape[2] - kernelSize + 2 * (kernelSize - 1)) / self.stride) + 1,
self.outputChannel)
"""
if method == "SAME":
self.output = np.zeros(
(self.batchSize, math.floor(self.height / self.stride), math.floor(self.width / self.stride),
self.outputChannel))
if method == "VALID":
self.output = np.zeros([self.batchSize, math.floor((self.height - kernelSize + 1) / self.stride),
math.floor((self.width - kernelSize + 1) / self.stride),
self.outputChannel])
def forward(self, x):
weights = self.weights.reshape([-1, self.outputChannel])
"""
if self.method == "FULL":
x = np.pad(x, (
(0, 0), (self.kernelSize - 1, self.kernelSize - 1), (self.kernelSize - 1, self.kernelSize - 1),
(0, 0)), 'constant', constant_values=0)
"""
if self.method == "SAME":
x = np.pad(x, (
(0, 0), (self.kernelSize // 2, self.kernelSize // 2), (self.kernelSize // 2, self.kernelSize // 2),
(0, 0)), 'constant', constant_values=0)
convOut = np.zeros(self.output.shape)
for i in range(self.batchSize):
img_i = x[i]
colImage_i = self.im2col(img_i, self.kernelSize, self.stride)
convOut[i] = np.reshape(np.dot(colImage_i, weights) + self.bias, self.output[0].shape)
return convOut
def im2col(self, image, kernelSize, stride):
imageCol = []
for i in range(0, image.shape[0] - kernelSize + 1, stride):
for j in range(0, image.shape[1] - kernelSize + 1, stride):
col = image[i:i + kernelSize, j:j + kernelSize, :].reshape([-1])
imageCol.append(col)
imageCol = np.array(imageCol)
return imageCol
inputData = np.random.random((4, 5, 5, 3))
print("inputShape: ", inputData.shape)
kernel = list([3, 3, 32])
print("kernel size: ", kernel)
conv2d = Conv2D(inputShape=inputData.shape, outputChannel=kernel[2], kernelSize=kernel[0], stride=1, method='VALID')
outputData = conv2d.forward(inputData)
print("outputShape: ", outputData.shape)
本文形状的命名方式为( batchsize , height , width , channels ) (\text {batchsize}, \text {height}, \text {width}, \text {channels})(batchsize ,height ,width ,channels ), 与Tensorflow中命名一致.
与Pytorch中的命名为( batchsize , channels , height , width ) (\text {batchsize}, \text {channels}, \text {height}, \text {width})(batchsize ,channels ,height ,width )有所不同.
重点:
由于图片转换后得到的矩阵为4维矩阵, 我们在进行计算处理的过程中会对矩阵进行 降维处理; 并且在进行矩阵乘法时, 也要注意两矩阵是否满足 矩阵乘法的条件.
参考资料:
https://zhuanlan.zhihu.com/p/63974249
https://blog.csdn.net/god_frey09/article/details/105188005
https://blog.csdn.net/v_JULY_v/article/details/51812459
https://www.jianshu.com/p/46b6615a7251
https://blog.csdn.net/dwyane12138/article/details/78449898
Original: https://blog.csdn.net/weixin_48320163/article/details/121097758
Author: uodgnez
Title: python实现卷积操作
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/688513/
转载文章受原作者版权保护。转载请注明原作者出处!