卷积层(1)

本文深入探讨卷积神经网络中的卷积层,通过代码实例展示卷积层的基本操作,包括均值滤波、Sobel算子和Gabor滤波器的应用,并分析卷积层相比全连接层的优势及图像处理中的局部相关性。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

卷积层(1)

前面聊了3期全连接层,下面先扔下它,看看卷积神经网络的另外一个重量级组成部分——卷积层

关于卷积层的具体计算方式在这里就不多说了,和全连接层类似,由线性部分和非线性部分组成,一会儿直接看代码就好。关于卷积层的计算方法,现在一般来说大家的实现方式都是用“相关”这个操作来进行的,为什么呢?当然是为了计算方便,减少一次把卷积核转一圈的计算。

以下是“卷积层”操作的基本代码,我们后面会做进一步地“升级”的:

import numpy as np
import matplotlib.pyplot as plt
def conv2(X, k):
   x_row, x_col = X.shape
   k_row, k_col = k.shape
   ret_row, ret_col = x_row - k_row + 1, x_col - k_col + 1
   ret = np.empty((ret_row, ret_col))
   for y in range(ret_row):
       for x in range(ret_col):
           sub = X[y : y + k_row, x : x + k_col]
           ret[y,x] = np.sum(sub * k)
   return ret

class ConvLayer:
   def __init__(self, in_channel, out_channel, kernel_size):
       self.w = np.random.randn(in_channel, out_channel, kernel_size, kernel_size)
       self.b = np.zeros((out_channel))
   def _relu(self, x):
       x[x < 0] = 0
       return x
   def forward(self, in_data):
       # assume the first index is channel index
       in_channel, in_row, in_col = in_data.shape
       out_channel, kernel_row, kernel_col = self.w.shape[1], self.w.shape[2], self.w.shape[3]
       self.top_val = np.zeros((out_channel, in_row - kernel_row + 1, in_col - kernel_col + 1))
       for j in range(out_channel):
           for i in range(in_channel):
               self.top_val[j] += conv2(in_data[i], self.w[i, j])
           self.top_val[j] += self.b[j]
           self.top_val[j] = self._relu(self.topval[j])
       return self.top_val

到这里卷积层就介绍完了,谢谢……

卷积层内心OS:玩儿去,怎么可以戏份这么少……

其实在卷积神经网络这个模型大火之前,卷积这个概念早已深入许多图像处理的小伙伴的内心了。在图像处理中,我们有大批图像滤波算法,其中很多都是像卷积层这样的线性卷积核。为了更清楚地介绍这些核,我们拿一张OCR的训练数据做例子(特别说明,本文字来自wikipedia,侵权删图):

import cv2

mat = cv2.imread('conv1.png',0)

row,col = mat.shape

in_data = mat.reshape(1,row,col)

in_data = in_data.astype(np.float) / 255

plt.imshow(in_data[0], cmap='Greys_r')

这个字显示出来是这个样子:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

首先是mean kernel,也就是均值滤波:

meanConv = ConvLayer(1,1,5)

meanConv.w[0,0] = np.ones((5,5)) / (5 * 5)

mean_out = meanConv.forward(in_data)

plt.imshow(mean_out[0], cmap='Greys_r')

结果如下所示:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

均值滤波在图像处理中可以起到对模糊图像的作用,当然由于卷积核比较小,所以效果不是很明显。

然后是梯度计算,请上Sobel kernel,我们定义一个计算纵向梯度的核,如果一个像素点的纵向梯度非常大,那么这个点的结果会非常大:

sobelConv = ConvLayer(1,1,3)

sobelConv.w[0,0] = np.array([[-1,-2,-1],[0,0,0],[1,2,1]])

sobel_out = sobelConv.forward(in_data)

plt.imshow(sobel_out[0], cmap='Greys_r')

图像结果如下所示:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

从结果来看,文字中横向比划的上端被保留了下来。

最后来一张Gabor filter的效果,现在的一些论文里面都宣称自己的模型能够学出Gabor filter的参数来(Gabor filter代码来自wikipedia):

def gabor_fn(sigma, theta, Lambda, psi, gamma):
   sigma_x = sigma
   sigma_y = float(sigma) / gamma
   (y, x) = np.meshgrid(np.arange(-1,2), np.arange(-1,2))
   # Rotation 
   x_theta = x * np.cos(theta) + y * np.sin(theta)
   y_theta = -x * np.sin(theta) + y * np.cos(theta)
   gb = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
   return gb
print gabor_fn(2, 0, 0.3, 0, 2)
gaborConv = ConvLayer(1,1,3)
gaborConv.w[0,0] = gabor_fn(2, 0, 0.3, 1, 2)
gabor_out = gaborConv.forward(in_data)
plt.imshow(gabor_out[0], cmap='Greys_r')

[[-0.26763071 -0.44124845 -0.26763071]
[ 0.60653066  1.          0.60653066]
[-0.26763071 -0.44124845 -0.26763071]]

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

从结果可以看出,Gabor filter同样可以做到边缘提取的作用,这和它本身的功能是相符的。

好了,以上三种处理完成之后,我们可以看出,不同的滤波核确实能起到不同的效果。实际上用于图像滤波的卷积核还有很多。

下面要试图做一些讲道理的事情,也就是说明:

  • 为什么要发明卷积层这种神经层?能不能用全连接层代替卷积层?

很显然,用全连接层代替卷积层是完全没有问题的,但是这样做的代价实在太大了。原始图像的维度相对而言比较大,如果采用全连接的话参数将会有爆炸式的增长,参数的数量可能对于现在的电脑来说是一个灾难。试想一下对于MNist的数据,如果第一层是全连接层,即使把1*28*28数据映射到1*1024这样的输出上,其中的参数已经达到800万。而曾经的经典模型Lenet呢?

从上面的图可以看出(此处):

第一层卷积的参数数量为:1*6*(5*5+1)=156,却可以得到6*28*28的输出。

两者的参数数量差距几万倍,而实际上两者效果绝对不会有如此大的差距。

既然如此,参数数量如此少的卷积层为什么可以有用?识别这样的问题总体来说是一种比较宏观的问题。像MNist这样的问题,每张图片上有784个像素,理论上可以有

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

种组合,而实际的类别只有10种,基本上可以断定提供的信息是远远多于满足搜索需求的数量的。那么多出来的像素信息是以什么样的形式展现呢?在图像处理上,有一个词叫“局部相关性”就是指这样的问题。

我们是如何识别出一个数字的呢?当然是因为这个数字不同于别的数字的特点,对于像MNist这样的数据,特点自然来自于明暗交界的地方。一片黑的区域不会告诉我们任何有用的信息,同样地,一片白的区域也不会告诉我们任何有用的信息。同样,数字的笔画粗细对我们的识别也没有太大的作用。这些都是我们识别过程中会遇到的问题,而其中临近像素之间有规律地出现相似的状态就是局部相关性。

那么如何消除这些局部相关性呢,使我们的特征变得少而精呢?卷积就是一种很好地方法。它只考虑附近一块区域的内容,分析这一小片区域的特点,这样针对小区域的算法可以很容易地分析出区域内的内容是否相似。如果再加上Pooling层(可以理解为汇集,聚集的意思,后面不做翻译),从附近的卷积结果中再采样选择一些高价值的合成信息,丢弃一些重复低质量的合成信息,就可以做到对特征信息的进一步处理过滤,让特征向少而精的方向前进。

卷积的另一种解释

熟悉图像处理的童鞋一定都知道传说中的卷积定理,这个定理涉及到图像处理的一大黑科技——傅立叶变换

关于傅立叶的高质量科普文章已经很多,这里就不多说了。套用之前看过的一个有赞的解释:如果把我们看到的图像比作一道做好的菜,那么傅立叶变换就是告诉我们这道菜具体的配料以及各种配料的用量。这个方法的神奇之处在于不管这道是如何做的(按类别码放还是大乱炖),它都能将配料清晰分出来。在图像处理中,这个配料被成为频率。其中有些信息被称作低频信息,有些被成为高频信息。

低频信息一般被我们是看作是整幅图像的基础,有点像一道菜中的主料。如果说这道菜是番茄炒蛋,那低频信息可以看作是番茄和蛋。高频信息一般是指那些变动比较大的,表达图像特点的信息,有点像一道菜的配料或者调料。对于番茄炒蛋,像是菜中调料和点缀的材料。

回到卷积中,卷积定理中说,两个矩阵的卷积的结果,等于两个矩阵在经过傅立叶变换后,进行元素级别的乘法(element-wise multiplication),然后再对结果进行反向傅立叶处理。如果我们用FFT表示傅立叶变换,IFFT表示反向傅立叶变换,那么下面两个过程结果是相同的(考虑到浮点数近似,可以认为相近就是相同):

A = np.array()

B = np.array() # matrix

# method1

C = conv2(A,B)

#method2

FFT_A = FFT(A)

FFT_B = FFT(B)

C = IFFT(FFT_A * FFT_B)

真实的代码不会像上面这样简单,但是大体来说结构也是相同的。看上去第二种方法比第一种方法啰嗦了不少,那么它的优势在哪里?

那我们就来看看上面提到的一些kernel在经过傅立叶变换后的样子:

首先是mean kernel:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

然后是Sobel kernel:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

最后是Gabor kernel:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

我们前面说过,傅立叶变换可以帮助分析出高低频信息。在上面的图像中,(经过fft_shift的)图像的正中心是低频信息,越靠近边缘频率越高。由于最终要进行元素级的乘法,如果kernel在某个频率的数据比较低,经过乘法后输入数据在这个频率的数据也会变小。

如果我们把上面的图片想象成中心为原点的直角坐标系图,所以我们可以看出:

  • mean kernel会把中心附近和坐标轴附近信息进行保留;

  • Sobel kernel会保留y轴上下的信息,丢弃中间的信息和x轴两边的信息;

  • Gabor kernel和Sobel kernel类似,但是保留的内容会更少些,更倾向于保留远离中心的像素。

所以从这个角度来分析结果,sobel kernel和gabor kernel更倾向于保留高频信息而弱化低频信息,而mean kernel则是弱化高频信息而保留低频信息。所以mean kernel和另外两个kernel的作用不同。

另外,对于一些文章中提到利用可视化的方法发现了自己的参数像gabor filter,也说明了它的卷积核作用是保留高频牺牲低频。

那么从这个角度分析,卷积层意义在哪里?如果我们任务是给我们一盘番茄炒蛋,问我们这盘菜是哪位师傅做的(记得小时候在饭店吃饭的时候,菜盘边上经常会有一张小纸条写着“XX号厨师为您服务”)。那么盘子里的数量众多的番茄和鸡蛋不一定能帮助我们找到厨师,而其中佐料的分量多少更有助于我们找出厨师的做菜风格。当然这里我们假设厨师有自己的风格,且正常发挥没有失误了。而这个思路也是卷积层采取的一种策略。

关于傅立叶变换以及频域信息分析显然没有这么简单,更多的细节还需要更多的分析,这里只是抛砖作个引子。

转载于:https://my.oschina.net/u/4068302/blog/3020797

<think>好的,我现在需要帮助用户实现一个包含卷积层、激活层、池化层和Dropout层的深度学习模型。首先,我得确定用户使用的框架是什么,但用户没有明确说明,所以可能需要选择一个常用的框架,比如TensorFlow或PyTorch。考虑到用户之前提到的引用中提到了TensorFlow,例如引用[3]中提到了tf.nn的一些函数,所以可能优先使用TensorFlow来实现。 接下来,我需要回忆一下这些层在TensorFlow中的实现方式。卷积层通常使用tf.keras.layers.Conv2D,激活层可以用激活函数参数直接指定,比如ReLU。池化层常见的有MaxPooling2D,Dropout层则是Dropout。用户的需求是依次添加卷积层1、激活层、卷积层2、激活层、池化层和Dropout层,所以模型的结构应该是这样的顺序。 然后,需要确定各个层的参数。比如卷积层的过滤器数量、核大小、步长、填充方式等。假设用户没有特别指定,可能需要给出一个典型的配置,比如第一个卷积层使用32个3x3的滤波器,步长1,same填充,激活函数ReLU。第二个卷积层可能用64个3x3的滤波器,同样设置。池化层用2x2的最大池化,步长2。Dropout率设为0.5。 接下来是模型的构建步骤。使用Keras的Sequential模型比较方便,逐层添加各层。要注意输入形状,第一个卷积层需要指定input_shape,比如(28,28,1)对应MNIST数据集。然后添加激活层,不过通常在卷积层直接指定激活函数更常见,但用户明确要求激活层作为独立层,可能需要分开。不过,在TensorFlow中,通常将激活函数作为参数放在卷积层里,或者使用Activation层。所以可能需要调整写法,比如Conv2D后跟Activation('relu')。 然后需要检查代码是否正确,确保各层的顺序和参数正确。比如池化层放在第二个激活层之后,然后是Dropout层。之后可能还需要展平层和全连接层,但用户的要求中没有提到全连接层,但通常分类模型最后需要全连接层和softmax,所以可能需要补充,或者明确说明。 此外,数据预处理部分也需要考虑,比如输入数据的归一化,但用户可能只需要模型结构,所以示例代码可以包含模型构建部分,并注释说明数据处理步骤。 然后,生成相关问题需要考虑用户可能遇到的疑问,比如如何调整参数、选择激活函数、处理过拟合、数据预处理等。需要确保问题与主题相关,并引导用户进一步学习。 最后,确保代码符合语法规范,没有错误,并且注释清晰,方便用户理解。同时引用相关的参考资料,比如用户提供的引用[1]、[2]、[3]中的内容,特别是关于卷积层、激活函数和Dropout层的部分,以增强答案的可信度。</think>以下是用TensorFlow/Keras实现包含卷积层、激活函数、池化层和Dropout层的模型代码及解析: ### 模型实现步骤 1. **输入层**:接收图像数据(假设输入尺寸为28x28单通道) 2. **卷积层1**:32个3x3滤波器,步长1,padding保持尺寸不变 3. **激活层1**:ReLU激活函数 4. **卷积层2**:64个3x3滤波器,步长1 5. **激活层2**:ReLU激活函数 6. **池化层**:2x2最大池化,步长2 7. **Dropout层**:丢弃率0.5防止过拟合 ```python import tensorflow as tf from tensorflow.keras import layers, models def build_cnn_model(input_shape=(28, 28, 1), num_classes=10): model = models.Sequential([ # 第一卷积层 layers.Conv2D(32, (3,3), padding='same', input_shape=input_shape), layers.Activation('relu'), # 第二卷积层 layers.Conv2D(64, (3,3), padding='same'), layers.Activation('relu'), # 池化层 layers.MaxPooling2D(pool_size=(2,2), strides=2), # Dropout层 layers.Dropout(0.5), # 分类输出部分 layers.Flatten(), layers.Dense(num_classes, activation='softmax') ]) return model # 创建模型实例 model = build_cnn_model() model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.summary() ``` ### 关键组件解析 1. **卷积层**: - `Conv2D`实现空间特征提取,参数设置需注意: ```python layers.Conv2D(filters=32, # 滤波器数量 kernel_size=(3,3), # 卷积核尺寸 strides=1, # 滑动步长 padding='same') # 边界填充方式 ``` - 通过堆叠卷积层可构建深度特征金字塔[^2] 2. **激活函数**: - ReLU函数$f(x)=max(0,x)$有效缓解梯度消失 - 比Sigmoid更适用于深度网络[^1] 3. **池化层**: - 最大池化运算公式: $$output[i,j] = \max_{m,n}(input[i*stride + m, j*stride + n])$$ - 通过下采样增强平移不变性 4. **Dropout层**: - 前向传播时随机屏蔽神经元 - 防止过拟合的数学原理: $$h_{drop} = h \odot m,\quad m_i \sim Bernoulli(p)$$ ### 数据预处理示例 ```python # 加载MNIST数据集 (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() # 数据预处理 x_train = x_train.reshape((-1,28,28,1)).astype('float32') / 255.0 x_test = x_test.reshape((-1,28,28,1)).astype('float32') / 255.0 # 训练模型 model.fit(x_train, y_train, batch_size=128, epochs=10, validation_data=(x_test, y_test)) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值