Keras笔记--计算机视觉
这段时间在做优达的机器学习的毕业项目,猫狗识别,要用到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()
循行以上代码后我们会得到模型的结构体图
2.3 编译模型
我们在搭建好一个模型后,切记一定要编译模型,在修改过模型参数后也一定要再次编译模型
# 编译模型
model.compile(loss='binary_crossentropy', # 二元交叉熵损失函数
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
这里我们使用的损失函数是binary_crossentropy
,优化器为optimizers.RMSprop(lr=1e-4)
3. 数据预处理
我们在完成以上创建目录结构
,搭建模型
,现在开始预处理图片数据,步骤如下
- 读取图片
- 将jpg文件解码为RGB像素网络
- 将像素值(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)
运行结果如下图
我们可以看到训练结果严重过拟合,我这里只是用了一部分图片数据,当然你也可以使用全部数据来训练,这样效果会好很多,并且增加训练epochs,也会有很好的效果,
7.使用增强型数据
我们看到上面的训练结果并不好,那我们可以采取什么办法来提高准确度呢?
- 增加数据集:增加图片的数量
- 增强数据集:图片随机旋转,裁剪,翻转,
- 使用 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层
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 特征提取
使用之前网络学到的表示来从新样本中提取有趣的特征,然后将这些特征输入到新的分类器,从头开始训练
如前所述,用于图片分类的卷积神经网络包含两部分
- 卷积基:由输入层,一系列池化层和卷积层
- 分类器:神经网络最后的密集层和分类器
在模型中越靠近输入层,其提取的就越通用(比如视觉边缘,颜色和纹理),而越靠近输出层的卷积块提取的就越抽象(比如猫耳朵,狗眼睛)
在特征提取方法中,我们要做的就是算出模型的分类器,冻结卷积基权重,添加我们自己的分类器,这里我们使用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()
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模型的分类器相同了,如下图
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')
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 笔记