【2019-07-16】深度学习用于计算机视觉

2019-08-04  本文已影响0人  BigBigFlower

1、卷积神经网络
一个简单的小例子🌰

#实例化一个小型的卷积神经网络
# Conv2D 层和 MaxPooling2D 层的堆叠

from keras import layers
from 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'))
卷积神经网络的架构,每个 Conv2D 层和 MaxPooling2D 层的输出都是一个形状为 (height, width, channels) 的 3D 张量。

将最后的输出张量[大小为 (3, 3, 64)]输入到一个密集连接分类器网络中, 即 Dense 层的堆叠。

#在卷积神经网络上添加分类器
model.add(layers.Flatten()) 
model.add(layers.Dense(64, activation='relu')) 
model.add(layers.Dense(10, activation='softmax'))
将进行 10 类别分类,最后一层使用带 10 个输出的 softmax 激活。网络的架构

在MNIST图像上训练卷积神经网络

from keras.datasets import mnist
from 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)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)
在测试数据上对模型进行评估
2、卷积运算
密集连接层和卷积层的根本区别在于,Dense 层从输入特征空间中学到的是全局模式,而卷积层学到的是局部模式,对于图像来说,学到的就是在输入图像的二维小窗口中发现的模式。
卷积神经网络的两个性质:
a 卷积神经网络学到的模式具有平移不变性
b 卷积神经网络可以学到模式的空间层次结构
卷积工作原理:
在 3D 输入特征图上滑动(slide)这些 3×3 或 5×5 的窗口,在每个可能 的位置停止并提取周围特征的 3D 图块[形状为 (window_height, window_width, input_ depth)]。然后每个 3D 图块与学到的同一个权重矩阵[叫作卷积核(convolution kernel)]做 2 张量积,转换成形状为 (output_depth,) 的 1D 向量。然后对所有这些向量进行空间重组, 使其转换为形状为 (height, width, output_depth) 的 3D 输出特征图。输出特征图中的 每个空间位置都对应于输入特征图中的相同位置(比如输出的右下角包含了输入右下角的信 息)。
卷积工作原理

3、最大池化运算
最大池化的作用:对特征图进行下采样,与步进卷积类似。
最大池化是从输入特征图中提取窗口,并输出每个通道的最大值。它的概念与卷积类似,但是最大池化使用硬编码的 max 张量运算对局部图块进行变换,而不是使用学到的线性变换(卷 积核)。最大池化与卷积的最大不同之处在于,最大池化通常使用 2×2 的窗口和步幅 2,其目 的是将特征图下采样 2 倍。与此相对的是,卷积通常使用 3×3 窗口和步幅 1。
4、在小型数据集上训练一个神经网络
猫狗分类数据集
将图像复制到训练、验证和测试的目录

import os, shutil
#数据目录
original_dataset_dir ="/Users/***/Desktop/learning_log/net_work/kaggle/train" 
 #小数据集目录
base_dir = '/Users/fanhua/Desktop/learning_log/net_work/kaggle/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)
#将前 1000 张猫的图像复制 到 train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)] 
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname) 
    dst = os.path.join(train_cats_dir, fname) 
    shutil.copyfile(src, dst)
#将接下来 500 张猫的图像复 制到 validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) 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)
#将接下来的 500 张猫的图像 复制到 test_cats_dir
fnames = ['cat.{}.jpg'.format(i) 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)
#将前 1000 张狗的图像复制 到 train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) 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)
#将接下来 500 张狗的图像复 制到 validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) 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)
#将接下来 500 张狗的图像复 制到 test_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)] 
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname) 
    dst = os.path.join(validation_dogs_dir, fname) 
    shutil.copyfile(src, dst)
数据

构建网络
卷积神经网络由 Conv2D 层(使用 relu 激活)和 MaxPooling2D 层交替堆叠构成。

from keras import layers
from keras import models
model = models.Sequential() 
model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3))) 
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(128, (3, 3), activation='relu')) 
model.add(layers.MaxPooling2D((2, 2))) 
model.add(layers.Conv2D(128, (3, 3), activation='relu')) 
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten()) 
model.add(layers.Dense(512, activation='relu')) 
model.add(layers.Dense(1, activation='sigmoid'))
特征图的维度随着每层变化

配置模型用于训练

from keras import optimizers
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])

数据预处理

(1) 读取图像文件
(2) 将 JPEG 文件解码为 RGB 像素网格
(3) 将这些像素网格转换为浮点数张量
(4) 将像素值(0~255 范围内)缩放到 [0, 1] 区间
使用 ImageDataGenerator 从目录中读取图像

from keras.preprocessing.image import ImageDataGenerator
#将所有图像乘以 1/255 缩放
train_datagen = ImageDataGenerator(rescale=1./255) 
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, 
                                                    target_size=(150, 150), #将所有图像的大小调整为 150×150
                                                    batch_size=20, 
                                                    class_mode='binary')
validation_generator = test_datagen.flow_from_directory(validation_dir,
                                                        target_size=(150, 150), 
                                                        batch_size=20, 
                                                        class_mode='binary')

利用批量生成器拟合模型,保存模型

history = model.fit_generator(train_generator,
                              steps_per_epoch=100,
                              epochs=30, 
                              validation_data=validation_generator,
                              validation_steps=50)
model.save('cats_and_dogs_small_1.h5')
30轮

绘制训练过程中的损失曲线和精度曲线

import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc'] 
loss = history.history['loss'] 
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc') 
plt.plot(epochs, val_acc, 'b', label='Validation acc') 
plt.title('Training and validation accuracy') 
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss') 
plt.plot(epochs, val_loss, 'b', label='Validation loss') 
plt.title('Training and validation loss')
plt.legend()
plt.show()
精度和损失图,过拟合

使用数据增强
过拟合是因为样本少(2000个),数据增强是从现有的样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加(augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察 到数据的更多内容,从而具有更好的泛化能力。
利用 ImageDataGenerator 来设置数据增强

datagen = ImageDataGenerator(rotation_range=40,#图像随机旋转角度范围
                             width_shift_range=0.2, #width_shift 和 height_shift 是图像在水平或垂直方向上平移的范围(相对于总宽
度或总高度的比例)。
                             height_shift_range=0.2, 
                             shear_range=0.2, #shear_range 是随机错切变换的角度。
                             zoom_range=0.2, #shear_range 是随机错切变换的角度。
                             horizontal_flip=True, #horizontal_flip 是随机将一半图像水平翻转。如果没有水平不对称的假设(比如真
实世界的图像),这种做法是有意义的。
                             fill_mode='nearest')#fill_mode 是用于填充新创建像素的方法,这些新像素可能来自于旋转或宽度 / 高度平移。 我们来看一下增强后的图像

显示几个随机增强后的训练图像

from keras.preprocessing import image#图像预处理模块
fnames = [os.path.join(train_cats_dir, fname) for  fname in os.listdir(train_cats_dir)]
img_path = fnames[3]#选一张图像进行增强
img = image.load_img(img_path, target_size=(150, 150))#读取图像并调整大小
x = image.img_to_array(img)#将其转换为形状 (150, 150, 3) 的 Numpy 数组
x = x.reshape((1,) + x.shape)#将其形状改变为 (1, 150, 150, 3)
i=0
#生成随机变换后的图像批量。 循环是无限的,因此需要在某个时刻终止循环
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0])) 
    i += 1
    if i % 4 == 0:
        break 
plt.show()
新生成的图像
网络看到的输入是高度相关的,因为这些输入都来自于少量的原始图像。无法生成新信息,而只能混合现有信息。为了进一步降低过拟合,还需要向模型中添加一个 Dropout 层,添加到密集连接分类器之前。
定义一个包含 dropout 的新卷积神经网络
model = models.Sequential() 
model.add(layers.Conv2D(32, (3, 3), 
                        activation='relu',
                        input_shape=(150, 150, 3))) 
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(128, (3, 3), 
                        activation='relu')) 
model.add(layers.MaxPooling2D((2, 2))) 
model.add(layers.Conv2D(128, (3, 3), 
                        activation='relu')) 
model.add(layers.MaxPooling2D((2, 2))) 
model.add(layers.Flatten()) 
model.add(layers.Dropout(0.5)) 
model.add(layers.Dense(512, activation='relu')) 
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', 
              optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])

利用数据增强生成器训练卷积神经网络

train_datagen = ImageDataGenerator( rescale=1./255,
                                   rotation_range=40, 
                                   width_shift_range=0.2,
                                   height_shift_range=0.2,
                                   shear_range=0.2, 
                                   zoom_range=0.2, 
                                   horizontal_flip=True,)
test_datagen = ImageDataGenerator(rescale=1./255)#注意,不能增强验证数据
train_generator = train_datagen.flow_from_directory(train_dir,
                                                    target_size=(150, 150), 
                                                    batch_size=32, 
                                                    class_mode='binary')
validation_generator = test_datagen.flow_from_directory( validation_dir,
                                                        target_size=(150, 150), 
                                                        batch_size=32, 
                                                        class_mode='binary')
history = model.fit_generator( train_generator,
                              steps_per_epoch=100,
                              epochs=100, 
                              validation_data=validation_generator, 
                              validation_steps=50)
model.save('cats_and_dogs_small_2.h5')

绘制图像


精度和损失

5、使用预训练的卷积神经网络
想要将深度学习应用于小型图像数据集,一种常用且非常高效的方法是使用预训练网络。 预训练网络(pretrained network)是一个保存好的网络,之前已在大型数据集(通常是大规模图像分类任务)上训练好。

特征提取
用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层,最后是一个密集连接分类器。第一部分叫作模型的卷积基(convolutional base)。对于卷积神经网 络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新数据,然后在输出上面训练一个新的分类器。
使用在 ImageNet 上训练的 VGG16 网络的卷积基从 猫狗图像中提取有趣的特征,然后在这些特征上训练一个猫狗分类器。

#将 VGG16 卷积基实例化
from keras.applications import VGG16
conv_base = VGG16(weights='imagenet', include_top=False,input_shape=(150, 150, 3))
conv_base.summary()
  1. 不使用数据增强的快速特征提取
    速度快,计算代价低
    使用预训练的卷积基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = '/Users/fanhua/Desktop/learning_log/net_work/kaggle/cats_and_dogs_small' 
train_dir = os.path.join(base_dir, 'train') 
validation_dir = os.path.join(base_dir, 'validation') 
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1./255) 
batch_size = 20
def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512)) 
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(
        directory, target_size=(150, 150), 
        batch_size=batch_size, 
        class_mode='binary')
    i=0
    for inputs_batch, labels_batch in generator:
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:
            break
    return features, labels
train_features, train_labels = extract_features(train_dir, 2000) 
validation_features, validation_labels = extract_features(validation_dir, 1000) 
test_features, test_labels = extract_features(test_dir, 1000)
#要将其输入到密集连接分类器中, 所以首先必须将其形状展平为 (samples, 8192)。
train_features = np.reshape(train_features, (2000, 4 * 4 * 512)) 
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512)) 
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

定义并训练密集连接分类器

from keras import models 
from keras import layers 
from keras import optimizers
model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512)) 
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer=optimizers.RMSprop(lr=2e-5), 
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(train_features, 
                    train_labels, 
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels))

绘制结果

import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc'] 
loss = history.history['loss'] 
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc') 
plt.plot(epochs, val_acc, 'b', label='Validation acc') 
plt.title('Training and validation accuracy') 
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss') 
plt.plot(epochs, val_loss, 'b', label='Validation loss') 
plt.title('Training and validation loss')
plt.legend()
plt.show()

2、使用数据增强的特征提取
速度慢、计算代价高
在卷积基上添加一个密集连接分类器

from keras import models
from keras import layers
model = models.Sequential() 
model.add(conv_base) 
model.add(layers.Flatten()) 
model.add(layers.Dense(256, activation='relu')) 
model.add(layers.Dense(1, activation='sigmoid'))
model.summary()

在编译和训练模型之前,一定要“冻结”卷积基。冻结(freeze)一个或多个层是指在训练过程中保持其权重不变。如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。 因为其上添加的 Dense 层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前学到的表示造成很大破坏。

print('This is the number of trainable weights before freezing the conv base:', len(model.trainable_weights))
conv_base.trainable = False
print('This is the number of trainable weights after freezing the conv base:', len(model.trainable_weights))

利用冻结的卷积基端到端地训练模型

from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
train_datagen = ImageDataGenerator(rescale=1./255,
                                   rotation_range=40, 
                                   width_shift_range=0.2, 
                                   height_shift_range=0.2, 
                                   shear_range=0.2, 
                                   zoom_range=0.2, 
                                   horizontal_flip=True, 
                                   fill_mode='nearest')
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir,
                                                    target_size=(150, 150),
                                                    batch_size=20,
                                                    class_mode='binary')
validation_generator = test_datagen.flow_from_directory( validation_dir,
                                                        target_size=(150, 150), 
                                                        batch_size=20, 
                                                        class_mode='binary')
model.compile(loss='binary_crossentropy', 
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])
history = model.fit_generator( train_generator,
                              steps_per_epoch=100,
                              epochs=30, 
                              validation_data=validation_generator, 
                              validation_steps=50)

模型微调(fine-tuning)
另一种广泛使用的模型复用方法是模型微调,与特征提取互为补充。


微调 VGG16 网络的最后一个卷积块

微调网络步骤:
(1) 在已经训练好的基网络(base network)上添加自定义网络。
(2) 冻结基网络。
(3) 训练所添加的部分。
(4) 解冻基网络的一些层。
(5) 联合训练解冻的这些层和添加的部分
卷积基架构:

conv_base.summary()

冻结直到某一层的所有层

conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1': 
    set_trainable = True
if set_trainable: 
    layer.trainable = True
else:
    layer.trainable = False

微调模型

model.compile(loss='binary_crossentropy', 
              optimizer=optimizers.RMSprop(lr=1e-5),
              metrics=['acc'])
history = model.fit_generator( train_generator,
                              steps_per_epoch=100,
                              epochs=100, 
                              validation_data=validation_generator, 
                              validation_steps=50)

绘制结果
使曲线平滑

def smooth_curve(points, factor=0.8): 
    smoothed_points = []
for point in points:
    if smoothed_points:
        previous = smoothed_points[-1]
        smoothed_points.append(previous * factor + point * (1 - factor))
    else: 
        smoothed_points.append(point)
return smoothed_points
plt.plot(epochs,smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs,smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy') 
plt.legend()
plt.figure()
plt.plot(epochs,
smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss') 
plt.legend()
plt.show()

卷积神经网络的可视化
可视化卷积神经网络的中间输出(中间激活)
可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层 的输出通常被称为该层的激活,即激活函数的输出)。

#加载模型
from keras.models import load_model 4 
model = load_model('cats_and_dogs_small_2.h5')
model.summary() 
#预处理单张图像

可视化卷积神经网络的过滤器
可视化图像中类激活的热力图

上一篇下一篇

猜你喜欢

热点阅读