Python深度学习之计算机视觉

本文介绍了使用Python进行深度学习,特别是卷积神经网络(CNN)在计算机视觉中的应用。从卷积神经网络的基础概念、卷积层、最大池化到在小型数据集上训练CNN,再到预训练网络的使用和微调,以及网络的可视化技巧,详细阐述了CNN如何处理图像数据。文章以MNIST和猫狗分类为例,展示了如何构建、训练和优化模型,以及如何通过数据增强和预训练模型提高模型性能。
摘要由CSDN通过智能技术生成

Deep Learning with Python for computer vision

这篇文章是我学习《Deep Learning with Python》(第二版,François Chollet 著) 时写的系列笔记之一。文章的内容是从 Jupyter notebooks 转成 Markdown 的,你可以去 GitHubGitee 找到原始的 .ipynb 笔记本。

你可以去这个网站在线阅读这本书的正版原文(英文)。这本书的作者也给出了配套的 Jupyter notebooks

本文为 第5章 深度学习用于计算机视觉 (Chapter 5. Deep learning for computer vision) 的笔记整合。

卷积神经网络简介

5.1 Introduction to convnets

卷积神经网络处理计算机视觉问题很厉害啦。

首先看一个最简单的卷积神经网络处理 MNIST 完爆第二章里的全连接网络的例子:

from tensorflow.keras import layers
from tensorflow.keras import models

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(64, (3, 3), activation='relu'))

model.add(layers.Flatten())
model.add(layers.Dense(64, activation="relu"))
model.add(layers.Dense(10, activation="softmax"))

这里我们用的 Conv2D 层要的 input_shape(image_height, image_width, image_channels) 这种格式的。

Conv2D 和 MaxPooling2D 层的输出都是 3D 张量 (height, width, channels), height 和 width 会逐层减小,channels 是由 Conv2D 的第一个参数控制的。

最后的三层里,我们是把最后一个 Conv2D 层的 (3, 3, 64) 的张量用一系列全连接层变成想要的结果向量:Flatten 层是用来把我们的 3D 张量展平(flatten,其实我想写成“压”、“降”之类的,这才是flatten的本意,但标准的中文翻译是展平)到 1D 的。 然后后面的两个 Dense 层就行我们在第二章做的那种,最后得到一个 10 路的分类。

最后,看一下模型结构:

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_3 (Conv2D)            (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
flatten_1 (Flatten)          (None, 576)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 64)                36928     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________

好了,网络就建成这样了,还是很简单的,接下来就训练它了,大致和之前第二章里的是一样的(但注意reshape的形状不一样):

# Load the TensorBoard notebook extension
# TensorBoard 可以可视化训练过程
%load_ext tensorboard
# Clear any logs from previous runs
!rm -rf ./logs/ 
# 在 MNIST 图像上训练卷积神经网络

import datetime
import tensorflow as tf

from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

# 准备 TensorBoard
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

model.compile(optimizer='rmsprop',
              loss="categorical_crossentropy",
              metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64,
          callbacks=[tensorboard_callback])
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 36s 599us/sample - loss: 0.0156 - accuracy: 0.9953
Epoch 2/5
60000/60000 [==============================] - 33s 554us/sample - loss: 0.0127 - accuracy: 0.9960
Epoch 3/5
60000/60000 [==============================] - 31s 524us/sample - loss: 0.0097 - accuracy: 0.9971
Epoch 4/5
60000/60000 [==============================] - 32s 529us/sample - loss: 0.0092 - accuracy: 0.9974
Epoch 5/5
60000/60000 [==============================] - 31s 523us/sample - loss: 0.0095 - accuracy: 0.9971





<tensorflow.python.keras.callbacks.History at 0x1441fa9d0>
%tensorboard --logdir logs/fit

(这理会输出 tensorboard 显示的可视化模型训练情况)

来在测试集看一下结果:

test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(test_loss, test_acc)
10000/1 - 1s - loss: 0.0172 - accuracy: 0.9926
0.03441549262946125 0.9926

卷积

卷积神经网络

我们之前用的密集连接层是在整个输入特征空间(在 MNIST 中就是所有的像素)中学习全局模式的;而这里的卷积层是学习局部模式的。也就是说,Dense 是学整个图像的,而 Conv 是学图像的局部,比如在我们刚才写的代码里是学了 3x3 的窗口:

卷积层学习局部模式

这种卷积神经网络具有两个性质:

  • 卷积神经网络学到的模式是平移不变的(translation invariant):卷积神经网络学习到某个模式之后,在其他地方又看到了这个一样的模式,它就会认出它已经学过这个了,不用再去学一次了。而对于 Dense 的网络,即使遇到有一样的局部部分依然要去重新学习一次。这个性质让卷积神经网络可以高效利用数据,它只需要更少的训练样本就可以学到泛化比较好的数据表示(一个个局部都记住了嘛,而不是靠整体去映射)。

  • 卷积神经网络可以学到模式的空间层次结构(spatial hierarchies of patterns):卷积神经网络在第一层学完了一个一个小的局部模式之后,下一层又可以用这些小局部拼出大一些的模式。然后这样多搞几层,卷积神经网络就可以学到越来越复杂、越来越抽象的视觉概念了,就是下面图片这个意思:

卷积神经网络可以学到模式的空间层次结构

卷积层

我们刚才例子中用来表示图片的那种 3D 张量,包括两个空间轴 height、width 和一个深度轴 depth(也叫 channels 轴),对于 RGB 图片,深度轴的维度就是3,分别表示3种颜色嘛;而对于 MNIST 这种灰度图片,深度就是1,只用一个数去表示灰度值。在这种3D张量和在上面做的卷积运算的结果被称作 feature map(特征图)。

卷积运算会从输入特征图中提取出一个个小分块,并对所有这些分块施加一个相同的变换,得到输出特征图。输出特征图仍是一个 3D 张量:具有宽度和高度,其深度可能是任意值,深度的大小是该层的一个参数,深度轴里的每个 channel 都代表一个 filter (过滤器)。filter 会对输入数据的某一方面进行编码,比如,某个过滤器可以编码“输入中包含一张脸”这种概念。

在刚才的 MNIST 例子中,第一个卷积层接受尺寸为 (28, 28, 1) 的输入特征图,输出一个尺寸为 (26, 26, 32) 的特征图。这个输出中包含 32 个 filter,在每个深度轴中的 channel 都包含有 26x26 的值,叫做 filter 对输入的响应图(response map),表示 filter 在输入中不同位置上的运算结果。这也就是特征图为什么叫特征图的原因了:深度轴的每个维度都是一个特征(或过滤器),而 2D 张量 output[:, :, n] 是这个过滤器在输入上的响应的二维空间图。

响应图的示意图

卷积运算

关于卷积,,emmm,复变没怎么听懂,我主要是看「知乎: 如何通俗易懂地解释卷积?」来理解的。这里我们主要用的是这种作用:

卷积

Keras 的 Conv2D 层初始化写成:

Conv2D(output_depth, (window_height, window_width))

其中包含了卷积运算有两个核心参数:

  • 输出特征图的深度:在我们刚才的 MNIST 例子里用了 32 和 64;
  • 从输入中提取的每个块(滑窗)的尺寸:一般是 3x3 或者 5x5;

卷积运算会像滑动窗口一样的遍历所有可能的位置,把输入中每一小块的特征 (window_height, window_width, input_depth) 通过与一个称作卷积核(convolution kernel)的要学习的权重矩阵做点乘,变化得到一个向量 (output_depth, )。所有的这种结果向量拼在一起就得到了一个 3D 的最终输出 (height, width, output_depth),其中的每个值就是输入对应过来的,比如取 3x3 的滑窗,则 output[i, j, :] 来自 input[i-1:i+1, j-1:j+1, :]

卷积的工作原理

关于卷积和 CNN 可以去看看这篇文章:Convolutional Neural Networks - Basics, An Introduction to CNNs and Deep Learning

注意,因为边界效应(border effects)和使用了步幅(strides),我们输出的宽度和高度可能与输入的宽度和高度不同。

边界效应和填充

边界效应就是你在做滑窗之后得到的矩阵大小会缩小一圈(边界没了)。例如现输入一个 5x5 的图片,取 3x3 的小块只能取出 9 块来,因此输出的结果为 3x3 的:

边界效应

之前我们做的 MNIST 也是类似的,一开始输入 28x28,第一层取 3x3 的,结果就是 26x26 了。

如果不希望这种减小发生,即希望保持输出的空间维度与输入的一致,则需要做填充(padding)。填充就是在输入的图片边界上加一些行和列,3x3 加1圈,5x5 要加2圈:

填充

Keras 的 Conv2D 层里可以用 padding 参数来设置使用填充。padding 可以设为:

  • "valid"(默认值):不做填充,只取“有效”的块。例如在 5×5 的输入特征图中,可以提取 3×3 图块的有效位置;
  • "same": 做填充,使输出和输入的 width、height 相等。
卷积步幅

卷积的步幅就是一次滑窗移多少,之前我们一直做的都是步幅为1的。我们把步幅大于 1 的卷积叫做步进卷积(strided convolution),比如下面这个是步幅为 2 的:

步幅为 2 的步进卷积

然而步进卷积在实际里面用的并不多😂,要做这种对特征图的下采样(downsampled)我们一般用最大池化。

注:

下采样:对于一个样值序列间隔几个样值取样一次,这样得到新序列就是原序列的下采样。

From 百度百科

最大池化

与步进卷积类似,最大池化是用来对特征图进行下采样的。在一开始的 MNIST 例子中,我们用了 MaxPooling2D 层之后,特征图的尺寸就减半了。

最大池化是在一个窗口里,从输入特征图取出每个 channel 的最大值,然后输出出来。这个运算和卷积很类似,不过施加的函数是一个 max。

最大池化我们一般都是用 2x2 的窗口,步幅为 2,这样取可以将特征图下采样2倍。(卷积是一般取3x3窗口和步幅1)

如果不用最大池化,直接把一大堆卷积层堆起来,会有两个问题:

  • 特征图尺寸下降的慢,搞到后面参数太多了,会加重过拟合;
  • 不利于空间层级结构的学习:一直一小点一小点的用卷积层去学,不利于看到更抽象的整体。

除了最大池化,下采样的方式还有很多:比如步进卷积、平均池化之类的。但一般用最大池化效果比较好,我们要的是知道有没有某个特征嘛,如果用平均去就把这个特征“减淡”了,如果用步进卷积又可能把这个信息错过了。

总而言之,使用最大池化/其他下采样的原因,一是减少需要处理的特征图的元素个数,二是通过让一系列的卷积层观察到越来越大的窗口(看到的覆盖越来越多比例的原始输入),从而学到空间层级结构。

在小型数据集上从头训练一个卷积神经网络

5.2 Training a convnet from scratch on a small dataset

我们搞计算机视觉的时候,经常要处理的问题就是在很小的数据集上训练一个图像分类的模型。emmm,这里的“很小”可以是几百到几万。

从这一节开始到后面几节,我们要搞的就是从头开始训练一个小型模型、使用预训练的网络做特征提取、对预训练的网络进行微调,这些步骤合起来就可以用于解决小型数据集的图像分类问题了。

我们这一节要做的是从头开始训练一个小型模型来分类猫狗的图片。不做正则化,先不管过拟合的问题。

下载数据

我们将使用 Dogs vs. Cats dataset 来训练模型,这个数据集里面是一大堆猫、狗的照片。这个数据集就不是 Keras 内置的了,我们可以从 Kaggle 下载:https://www.kaggle.com/c/dogs-vs-cats/data

下载下来解压缩,,,(emmmm有点大我的 MBP 都装不下了,放移动硬盘上才解出来的😂,emmmm,又想起来该买个新固态了)。

然后来创建我们要用的数据集:训练集猫狗各1000个样本,验证集各500个,测试集各500个。编程来完成这个工作:

# 将图像复制到训练、验证和测试的目录
import os, shutil

original_dataset_dir = '/Volumes/WD/Files/dataset/dogs-vs-cats/dogs-vs-cats/train'    # 原始数据集

base_dir = '/Volumes/WD/Files/dataset/dogs-vs-cats/cats_and_dogs_small'    # 将要保存的较小数据集的位置
os.mkdir(base_dir)


# 创几个目录放划分后的训练、验证和测试集
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

# 分开放猫狗
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

# 复制猫的图片
fnames = [f'cat.{i}.jpg' for i in range(1000)]    # 这里用了 f-String,要求 Python >= 3.6,老版本可以用 'cat.{}.jpg'.format(i)
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)
    
fnames = [f'cat.{i}.jpg' for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)
    
fnames = [f'cat.{i}.jpg' for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)
    
# 复制狗的图片
fnames = [f'dog.{i}.jpg' for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)
    
fnames = [f'dog.{i}.jpg' for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)
    
fnames = [f'dog.{i}.jpg' for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)
    
# 检查
print('total training cat images:', len(os.listdir(train_cats_dir)))
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
print('total test cat images:', len(os.listdir(test_cats_dir)))

print('total training dog images:', len(os.listdir(train_dogs_dir)))
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
print('total test dog images:'
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值