Keras笔记--计算机视觉

版权声明:原创作品,欢迎转载 https://blog.csdn.net/xf8964/article/details/88771239


这段时间在做优达的机器学习的毕业项目,猫狗识别,要用到Keras库,并且用 Keras的卷积神经网络来做图片识别,在网上看到《python 深度学习》作者为 keras之父,这本书很通俗易懂的讲解了神经网络,卷积神经网络,并从零搭建卷积神经网络开始,一直到使用 VGG16模型来迁移学习,详细的讲解了怎么使用 Keras来创建我们的CNN网络模型处理图片,下面开始我们学习

1.数据集处理

这里我们使用 Kaggle上的 Dogs vs Cats 项目来完成我们的实验,你可以在Kaggle上下载数据集,该数据集解压后又两个文件夹,一个是train目录和test目录,在train目录下有猫和狗的图片,命名规则为type.number.jpg。type为cat和dog,number为图片的编号,得到数据集后我们需要将它处理为我们的目录形式,我们使用数据集的一部分来做我们的实验,如下
├── cat_and_dog_small
└─── train [2000 images]
└─ cat [1000images]
└─ dog [1000images]
└─── validation [1000images]
└─ cat [500images]
└─ dog [500images]
└── test [1000images]
└─ cat [500images]
└─ dog [500images]
我们可以通过代码来实现我们的目录结构,代码如下

1.1创建目录结构

# 处理文件/目录模块
import os
# shutil 是高级的文件,文件夹,压缩包处理模块
import shutil
from tqdm import tqdm
# 指定图片目录
original_dataset_dir = './train/'
# 创建小数据集目录,定指定该目录
base_dir = './cat_and_dog_small'
# 训练目录
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_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_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_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_dir = os.path.join(base_dir, 'test')
os.mkdir(test_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)

1.2 拷贝图片

上面我们创建了我们的目录结构,接下来我们将图片数据拷贝到我们的对应的目录中,同样,我们也要用代码来实现它

# 复制训练图片,猫狗各一千张
for i in tqdm(range(1000)):
    shutil.copy('./train/cat.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/train/cats')
    shutil.copy('./train/dog.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/train/dogs')
# 复制验证图片,猫狗各500张
for i in tqdm(range(1000, 1500)):
    shutil.copy('./train/cat.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/validation/cats')
    shutil.copy('./train/dog.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/validation/dogs')
# 复制测试图片,猫狗各500张
for i in tqdm(range(1500, 2000)):
    shutil.copy('./train/cat.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/test/cats')
    shutil.copy('./train/dog.{}.jpg'.format(i) ,
    			 './cat_and_dog_small/test/dogs')

1.3 展示图片数据

print('total train cat images: ', len(os.listdir(train_cats_dir)))
print('total train dog images: ', len(os.listdir(train_dogs_dir)))

print('total validation cat images: ', len(os.listdir(validation_cats_dir)))
print('total validation dog images: ', len(os.listdir(validation_dogs_dir)))

print('total test cat images: ', len(os.listdir(test_cats_dir)))
print('total test dog images: ', len(os.listdir(test_dogs_dir)))

运行代码后得到结果如下
total train cat images: 1000
total train dog images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500

2. 搭建网络

现在我们使用 Keras来搭建我们的CNN网络

2.1 导入模块

from keras import layers
from keras import models
from keras import optimizers   # 优化器
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import matplotlib.image as mpimg # mpimg 用于读取图片
import numpy as np
from keras.callbacks import ModelCheckpoint, EarlyStopping
import time
%matplotlib inline

2.2 搭建第一个CNN网络

我们使用 Sequential 来创建我们的模型对象,我们使用最大池化层,

问题类型 最后一层激活 损失函数
二分类问题 sigmoid binary_crossentropy
多分类,单标签问题 softmax categorical_crossentropy
多分类,多标签问题 sigmoid binary_crossentropy
回归到任意值 mse
回归到0~1范围内的值 sigmoid mse 或 binary_crossentropy
# 构建一个网络模型对象
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'))

model.summary()

循行以上代码后我们会得到模型的结构体图
CNN网络参数模型

2.3 编译模型

我们在搭建好一个模型后,切记一定要编译模型,在修改过模型参数后也一定要再次编译模型

# 编译模型
model.compile(loss='binary_crossentropy', # 二元交叉熵损失函数
                         optimizer=optimizers.RMSprop(lr=1e-4),
                         metrics=['acc'])

这里我们使用的损失函数是binary_crossentropy,优化器为optimizers.RMSprop(lr=1e-4)

3. 数据预处理

我们在完成以上创建目录结构搭建模型,现在开始预处理图片数据,步骤如下

  1. 读取图片
  2. 将jpg文件解码为RGB像素网络
  3. 将像素值(0 – 255)范围缩放到(0 – 1),有的模型自带了输入数据预处理函数
    Keras中提供了这些模块,在keras.preprocessing.image,其中包含了ImageDataGenerator类,可以快速创建 Python生成器,能够将硬盘上的图片转换为预处理的张量批量,代码如下
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),	# 模型输入图片大小(height, width)
                                batch_size=20,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签

validation_generator = test_datagen.flow_from_directory(
                                validation_dir,
                                target_size=(150, 150),
                                batch_size=20,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签

运行以上代码后,返回train_generator对象和validation_generator对象,我们来观察一下返回的对象是什么样子的

# 打印前10个图片的类别,0表示猫,1表示狗
print(train_generator.classes[0:10])
# 打印前10个图片的名称
print(train_generator.filenames[0:10])
# 打印前10个图片的路径
print(train_generator.filepaths[0:10])

train_generator是一个迭代对象,我们可以通过以下代码来看一下这个迭代对象是什么样子的

for data_batch, labels_batch in train_generator:
    print('data batch shape', data_batch.shape)
    print('labels batch shape', labels_batch.shape)
    break

data batch shape (20, 150, 150, 3)
labels batch shape (20,)
以上是我们运行代码后得到的结果,可以看到迭代data_batch为20张形状为(150, 150, 3)的图片,其中3为通道, labels_batch 为图片标签

4. 拟合数据

这里使用 fit_generator 方法来拟合数据,我觉得也应该可以叫做训练模型

history = model.fit_generator(
            train_generator,               # 训练数据生成器
            steps_per_epoch=100,    # 批次数量,这里我们有2000个训练样本图片,上面我们选择 				   batch_size 为20,那么批次数量就是 2000/20=100
            epochs=30,                     # 训练 循环数量,训练完成2000个图片是一个epochs
            validation_data=validation_generator,  # 验证数据生成器 
            validation_steps=50)      # 验证批次数量,同上面训练批次数量

5. 保存模型

训练完毕后,我们需要将训练的模型保存你,因为训练模型是一个很耗时间的事情,所以保存模型后,在下一次就不用重新训练

model.save('cats_and_dogs_small_1.h5')

6. 可视化训练过程 – 损失曲线和精度曲线

为了可视化模型的训练过程,并展示模型是怎么一步步符合我们的要求,并且怎么一步步找到每一层合适的权重或过滤器的,这里我们选择可视化 [‘acc’, ‘loss’, ‘val_acc’, ‘val_loss’] 训练结果数据
一下我们定义可视化数据的函数,参数为训练返回的对象

# ['acc', 'loss', 'val_acc', 'val_loss']
def plot_train_data(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
 
    epochs = range(len(acc))
    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b', label='Validation acc')
    plt.title('Training and validation accuracy')
    #plt.ylim(0.7, 1)
    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.ylim(0, 1)
    plt.figure()
    
    plt.show()  

接下来我们就展示上面的训练结果

plot_train_data(history)

运行结果如下图
训练结果1
我们可以看到训练结果严重过拟合,我这里只是用了一部分图片数据,当然你也可以使用全部数据来训练,这样效果会好很多,并且增加训练epochs,也会有很好的效果,

7.使用增强型数据

我们看到上面的训练结果并不好,那我们可以采取什么办法来提高准确度呢?

  1. 增加数据集:增加图片的数量
  2. 增强数据集:图片随机旋转,裁剪,翻转,
  3. 使用 dropout 层

7.1 修改网络结构,添加 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())
# 添加dropout层
model.add(layers.Dropout(0.5))
# 添加输出稠密层
model.add(layers.Dense(512, activation='relu'))
# 添加输出层,输出结果为一维
model.add(layers.Dense(1, activation='sigmoid'))

# 展示模型
model.summary()

# 编译模型
model.compile(loss='binary_crossentropy', # 二元交叉熵损失函数
                         optimizer=optimizers.RMSprop(lr=1e-4),
                         metrics=['acc'])

运行结果如下图,我们可以看到添加的 dropout层
添加 dropout 层模型

7.2 使用增强数据

使用ImageDataGenerator函数来增强数据集
参数说明

  • rotation_range:图像随机旋转角度
  • width_shift_range:图片在水平上的随机平移
  • height_shift_range:图片在垂直方向的随机平移
  • shear_range: 随机错切变换角度
  • zoom_range:图片随机缩放
  • horizontal_flip:随机将图片一般水平反转
  • fill_mode:用于填充新添加的像素的方法
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=20,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签

validation_generator = test_datagen.flow_from_directory(
                                validation_dir,
                                target_size=(150, 150),
                                batch_size=20,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签

7.3 拟合数据–训练模型

history = model.fit_generator(
            train_generator,               # 训练数据生成器
            steps_per_epoch=100,    # 批次数量,这里我们有2000个训练样本图片,上面我们选择 batch_size 为20,那么批次数量就是 2000/20=100
            epochs=30,                     # 训练 循环数量,训练完成2000个图片是一个epochs
            validation_data=validation_generator,  # 验证数据生成器 
            validation_steps=50)      # 验证批次数量,同上面训练批次数量

7.4 保存模型

我们将在可视化卷积模型部分用到该模型

model.save('cats_and_dogs_small_2.h5')

7.5 可视化训练结果

plot_train_data(history)

使用增强数据训练结果
通过对比,这次的训练结果明显没有过拟合,比上次改善很多

8. 使用迁移学习训练网络

前面我们从零构建了我们自己的CNN网络,了解了CNN网络的最简单的模型结构,但训练结果虽然有所提上,但是准确度并不算高,所以我们需要通过迁移学习,使用大牛们为训练好的模型和权重来构建我们的模型,迁移学习一般是删除模型的输出,添加适合自己的输出模型
方法有两种,特征提取微调模型

8.1 特征提取

使用之前网络学到的表示来从新样本中提取有趣的特征,然后将这些特征输入到新的分类器,从头开始训练
如前所述,用于图片分类的卷积神经网络包含两部分

  1. 卷积基:由输入层,一系列池化层和卷积层
  2. 分类器:神经网络最后的密集层和分类器
    在模型中越靠近输入层,其提取的就越通用(比如视觉边缘,颜色和纹理),而越靠近输出层的卷积块提取的就越抽象(比如猫耳朵,狗眼睛)
    在特征提取方法中,我们要做的就是算出模型的分类器,冻结卷积基权重,添加我们自己的分类器,这里我们使用VGG16做我们的迁移学习,因为VGG16模型和我们上面建立的卷积网络比较相似,也最好理解

8.1.1 导入模型

首先我们要导入VGG模型

from keras.applications import VGG16

8.1.2 创建VGG16对象

然后使用VGG16模型来实例化我们的对象

  • weights=‘imagenet’,使用 imagenet 的权重
  • include_top=False, 不是用最后的全连接层
  • input_shape 定义输入数据形状
conv_base = VGG16(weights='imagenet', include_top=False,
                 input_shape=(150, 150, 3))

来看一下VGG16模型

conv_base.summary()

VGG16

8.1.3 创建分类器

model = models.Sequential()
# 添加卷积基,作为我们的迁移学习模型
model.add(conv_base)
# 之后添加我们的输出链接层
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
# 展示模型
model.summary()

这里我们在第一层使用上面我们导入的VGG16模型的卷积基,之后就和我们上面创建CNN模型的分类器相同了,如下图
加入VGG16的模型

8.1.4 冻结权重和编译模型

在训练模型之前,我们需要冻结卷积基的权重,因为我们不需要训练卷积基的权重,这些权重的都是训练好了的,

conv_base.trainable = False
# 编译模型
model.compile(loss='binary_crossentropy', # 二元交叉熵损失函数
                         optimizer=optimizers.RMSprop(lr=1e-4),
                         metrics=['acc'])

8.1.5 加载数据和训练模型

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=40,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签

validation_generator = test_datagen.flow_from_directory(
                                validation_dir,
                                target_size=(150, 150),
                                batch_size=40,
                                class_mode='binary') # 因为使用了 binary_crossentropy 二元交叉熵作为损失函数,所以标签需要是二进制标签
                                
start_time = int(time.time())
print('***********************************************************')
print('开始训练')
print('***********************************************************')
history = model.fit_generator(
            train_generator,               # 训练数据生成器
            steps_per_epoch=100,    # 批次数量,这里我们有2000个训练样本图片,上面我们选择 batch_size 为20,那么批次数量就是 2000/20=100
            epochs=30,                     # 训练 循环数量,训练完成2000个图片是一个epochs
            validation_data=validation_generator,  # 验证数据生成器 
            validation_steps=50)      # 验证批次数量,同上面训练批次数量
end_time = int(time.time())
use_time = end_time - start_time
print('***********************************************************')
print('训练结束', use_time)
print('***********************************************************')

plot_train_data(history)

训练完成后,得到图形如下
在这里插入图片描述
我们可以看到训练结果有了很大的提升

8.1.6 保存模型,测试模型

model.save('cats_and_dogs_small_3.h5')
test_generator = test_datagen.flow_from_directory(
                            test_dir,
                            target_size=(150, 150),
                            batch_size=20,
                            class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print("test acc: ", test_acc)

Found 1000 images belonging to 2 classes.
test acc: 0.8889999997615814
上面我们使用了test目录的图片来测试模型,现在我们来测试一张图片

8.1.7 判断一张图片

from keras.preprocessing import image                  
from tqdm import tqdm

def path_to_tensor(img_path):
	"""记载一张图片的张量"""
    # 用PIL加载RGB图像为PIL.Image.Image类型
    img = image.load_img(img_path, target_size=(150, 150))
    # 将PIL.Image.Image类型转化为格式为(224, 224, 3)的3维张量
    x = image.img_to_array(img)
    # 将3维张量转化为格式为(1, 224, 224, 3)的4维张量并返回
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
	"""加载一个目录中所有图片为张量"""
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)
def predide_dog_cat(path):
	"""预测一张图片的类别"""
    x = path_to_tensor(path)
    y = model.predict(x)
    if y>0.7:
        print('this is a dog!', y)
    else:
        print('this is a cat!', y)
    plt.title(path)
    plt.imshow(x[0]) # 显示图片
    plt.axis('off') # 不显示坐标轴
    
predide_dog_cat('./test/800.jpg')

预测1

8.2 微调模型

预训练网络中,越靠近输入层的卷积块学到的就越通用(一般为视觉边缘,颜色和纹理),越靠近输出层的卷积块就越具体(比如耳朵,眼睛,脚等具体的视图),所以我们要解冻靠近输出层的卷积块。
微调网络步骤:

  • 在已经能够训练好的基网络上添加自己定义的网络,比如前面我们删掉VGG16的输出层,定义我们自己的输出层
  • 冻结基网络
  • 训练所添加的部分
  • 解冻基网络的一些层
  • 联合训练解冻的这些层和添加的部分

8.2.1 解冻靠近分类器的卷积块

解冻VGG16模型的 block5_conv1 层

conv_base.trainable = True

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

# 编译模型 ,每次修改模型就要重新编译
model.compile(loss='binary_crossentropy', # 二元交叉熵损失函数
                         optimizer=optimizers.RMSprop(lr=1e-4),
                         metrics=['acc'])

8.2.2 训练模型

history = model.fit_generator(
            train_generator,               # 训练数据生成器
            steps_per_epoch=100,    # 批次数量,这里我们有2000个训练样本图片,上面我们选择 batch_size 为20,那么批次数量就是 2000/20=100
            epochs=30,                     # 训练 循环数量,训练完成2000个图片是一个epochs
            validation_data=validation_generator,  # 验证数据生成器 
            validation_steps=50)      # 验证批次数量,同上面训练批次数量
model.save('cats_and_dogs_small_4.h5')

plot_train_data(history)

微调模型可视化

test_generator = test_datagen.flow_from_directory(
                            test_dir,
                            target_size=(150, 150),
                            batch_size=20,
                            class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print("test acc: ", test_acc)

Found 1000 images belonging to 2 classes.
test acc: 0.935999995470047
这次测试结果得分是最高的

结束

到这里就结束了,执行问本文代码后,你就可以理解怎么使用 Keras来处理图片了,之后我还会持续更新 Keras的用法,可以在我的github上下载完整代码
Keras 笔记

猜你喜欢

转载自blog.csdn.net/xf8964/article/details/88771239