tensorflow系列之6:CNN

import numpy as np
import tensorflow as tf
import sklearn
import sys
import matplotlib.pyplot as plt
from tensorflow import keras
print(tf.__version__)
print(sys.version)

# To plot pretty figures
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
2.5.0
3.8.8 (default, Apr 13 2021, 19:58:26) 
[GCC 7.3.0]

本文主要介绍tensorflow中关于CNN的一些基础功能。

1、滤波器与特征图

我们先看一下图画经过滤波器后输出的特征图。

我们定义了2个77的滤波器,第一个的中间竖线为1,其余部分为0;第二个滤波器的中间横线为1,其余部分为0。
特征图上每一点的数值等于其对应的7
7图象的加权求和,而由于第一个滤波器除中间竖线以外的数值都为0,所以特征图的数值输出只与中间的7个点相关。所以对于第一个滤波器,白色竖线被加强了,而其余部分变得模糊。同理,对于第二个滤波器,白色横线被加强了,其余部分变得模糊。

注意,这里为了展示滤波器的作用,我们自己定义了一个滤波器。而在模型训练中,滤波器的权重一般是可训练的。

# 加载图片
from sklearn.datasets import load_sample_image
china = load_sample_image('china.jpg') / 255
flower = load_sample_image('flower.jpg') / 255
images = np.array([china, flower])
batch_size, height, width, channels = images.shape
print(batch_size, height, width, channels)

# 创建2个滤波器
filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters[:, 3, :, 0] = 1 #垂直滤波器
filters[3, :, :, 1] = 1 #水平滤波器

outputs = tf.nn.conv2d(images, filters, strides=1, padding='SAME')

plt.imshow(outputs[0,:,:,1], cmap="gray")
plt.axis('off')
plt.show()

2 427 640 3

让我们看一下这段代码:

  • 每个颜色通道的像素强度表示为从0到255的字节,因此我们除以255即可缩放这些特征,得到从0到1的浮点数。
  • 然后,我们创建两个7×7的滤波器(一个在中间带有垂直白线,另一个在中间带有水平白线)。
  • 我们使用tf.nn.conv2d()函数应用于两个图像,这是TensorFlow的低层深度学习API的一部分。在此示例中,我们使用零填充(padding=“SAME”)和步幅为1。
  • 最后,绘制一个结果特征图。

f.nn.conv2d()行值得更多解释:

  • images是输入的小批量(4D张量,如前所述)。
  • filter是要应用的一组滤波器(也是4D张量,如前所述)。
  • strides等于1,但也可以是包含四个元素的一维度组,其中两个中间元素是垂直步幅和水平步幅(sh和sw)。第一个元素和最后一个元素必须等于1。它们可能有一天用于指定批处理步幅(跳过某些实例)和通道步幅(跳过某些上一层的特征图或通道)。
  • padding必须为"SAME"或"VALID":
  • 如果设置为"SAME",则卷积层在必要时使用零填充。将输出大小设置为输入神经元的数量除以步幅(向上取整)所得的值。例如,如果输入大小为13而步幅为5,则输出大小为3(即13/5=2.6,向上舍入为3)。然后根据需要在输入周围尽可能均匀地添加零。当strides=1时,层的输出将具有与其输入相同的空间尺寸(宽度和高度),因此命名为“same”。
  • 如果设置为"VALID",则卷积层将不使用零填充,并且可能会忽略输入图像底部和右侧的某些行和列,具体取决于步幅,(为简单起见,仅这里显示了水平尺寸,当然垂直尺寸也适用相同的逻辑。这意味着每个神经元的接受野都严格位于输入内部的有效位置内(不会超出范围),因此命名为“valid”。


此示例中,我们手动定义了滤波器,但是在实际的CNN中,你通常将滤波器定义为可训练变量,以便神经网络可以了解哪种滤波器效果最好。

下面我们把4个特征图都画出来:

for image_index in (0, 1):
    for feature_map_index in (0, 1):
        plt.subplot(2, 2, image_index*2+feature_map_index+1)
        plt.imshow(outputs[image_index, :, :, feature_map_index], cmap='gray')

plt.show()

我们对看一下图像的局部:

def crop(images):
    return images[150:220, 130:250]

plt.imshow(crop(images[0, :, :, 0]))
#save_fig("china_original", tight_layout=False)
plt.show()

for feature_map_index, filename in enumerate(["china_vertical", "china_horizontal"]):
    plt.imshow(crop(outputs[0, :, :, feature_map_index]))
    #save_fig(filename, tight_layout=False)
    plt.show()

最后,我们画一下上面的那2个滤波器:

plt.imshow(filters[:, :, 0, 0], cmap='gray')
plt.show()
plt.imshow(filters[:, :, 0, 1], cmap='gray')
plt.show()

卷积层

正如上面所言,在实际的CNN中,你通常将滤波器定义为可训练变量,以便神经网络可以了解哪种滤波器效果最好,如前所述。不用手动创建变量,使用keras.layers.Conv2D层。

这段代码使用步幅为1(水平和垂直)和"same"的填充,创建了一个包含32个滤波器(每个3×3)的Conv2D层,并将ReLU激活函数应用于其输出。如你所见,卷积层具有很多超参数:你必须选择滤波器的数量、其高度和宽度、步幅和填充类型。与往常一样,你可以使用交叉验证来找到正确的超参数值,但这非常耗时。稍后我们将讨论通用的CNN架构,使你了解哪些超参数值在实践中最有效。

conv = keras.layers.Conv2D(filters=32, kernel_size=3, strides=1,
                           padding="SAME", activation="relu")

2、池化层

池化层的目标是对输入图像进行下采样(即缩小),以便减少计算量、内存使用量和参数数量(从而降低过拟合的风险)。

就像在卷积层中一样,池化层中的每个神经元都连接到位于一个小的矩形接受野中的上一层中有限数量的神经元的输出。你必须像以前一样定义其大小、步幅和填充类型。但是,池化神经元没有权重。它所做的全部工作就是使用聚合函数(例如最大值或均值)来聚合输入。图148显示了最大池化层,它是最常见的池化层类型。在此示例中,我们使用2×2池化内核,步幅为2,没有填充。只有每个接受野中的最大输入值才能进入下一层,而其他输入则被丢弃。例如,在图148的左下接受野中,输入值为1、5、3、2,因此只有最大值5传播到下一层。由于步幅为2,因此输出图像的高度为输入图像的一半,宽度为输入图像的一半(由于不使用填充,因此四舍五入)。

池化层通常独立地作用于每个输入通道,因此输出深度与输入深度相同。

不变性

除了减少计算量、内存使用量和参数数量之外,最大池化层还为小变换引入了一定程度的不变性,如图149所示。在这里,我们假设亮像素的值比暗像素的值低,并且我们考虑三个图像(A,B,C)经过最大池化层,其内核为2×2,步幅为2。图像B和C与图片A相同,但向右移动一两个像素。如你所见,图像A和B的最大池化层的输出是相同的。这就是变换不变性的含义。对于图像C,输出是不同的:它向右移动了一个像素(但仍然有75%的不变性)。通过在CNN中每隔几层插入一个最大池化层,就可以在更大范围内获得某种程度的变换不变性。而且最大池化提供了少量的旋转不变性和轻微的尺度不变性。在不应该依赖这些细节的预测情况下(例如在分类任务中),这种不变性(即使有局限性)也很有用。

要创建平均池化层,只需使用AvgPool2D而不是MaxPool2D。如你所料,它的工作原理与最大池化层完全相同,只是它计算的是平均值而不是最大值。平均池化层曾经很受欢迎,但是现在人们通常都使用最大池化层,因为它们通常表现更好。这似乎令人惊讶,因为计算均值通常比计算最大值损失更少的信息。但是另一方面,最大池化仅保留最强的特征,将所有无意义的特征都丢弃掉,因此下一层可获得更清晰的信号来使用。而且与平均池化相比,最大池化提供了更强的变换不变性,并且所需计算量略少于平均池化。

在现代架构中经常会看到的最后一种类型的池化层是全局平均池化层。它的工作原理非常不同:它所做的是计算整个特征图的均值(这就像使用与输入有相同空间维度的池化内核的平均池化层)。这意味着它每个特征图和每个实例只输出一个单值。尽管这是极具破坏性的(特征图中的大多数信息都丢失了),但它可以用作输出层,正如我们将在本章稍后看到的那样。要创建这样的层,只需使用keras.layers.GlobalAvgPool2D类。

max_pool=keras.layers.MaxPool2D(pool_size=2)
max_pool=keras.layers.AvgPool2D(pool_size=2)
global_avg_pool=keras.layers.GlobalAvgPool2D()

最大池化层

max_pool = keras.layers.MaxPool2D(pool_size=2)
cropped_images = np.array([crop(image) for image in images], dtype=np.float32)
output = max_pool(cropped_images)

fig = plt.figure(figsize=(12, 8))
gs = mpl.gridspec.GridSpec(nrows=1, ncols=2, width_ratios=[2, 1])

ax1 = fig.add_subplot(gs[0, 0])
ax1.set_title("Input", fontsize=14
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值