![d38c39c79cf6d357b0f35769307c8060.png](https://i-blog.csdnimg.cn/blog_migrate/66342093582a7afee15315c18d27d874.jpeg)
这个系列记录下自己的深度学习练习,本文算是对上一篇文章的补充,算是稍微进阶一些的图像处理方法。之前的模型都是自己从头开始构建,如果网络上有别人已经写好的模型,我们就可以对其微调,然后使用自己的数据集进行分析,可以节省构建模型的步骤。而且这些外部模型一般样本量很大,算是真正经过大数据考验的模型,如果和你在分析的问题类似,且你自己手头上的数据较少,用这种预处理的模型就非常有效。
使用预训练网络有两种方法:特征提取和微调模型。
本文使用VGG16模型作为预训练的模型,用来预测上篇文章的猫狗问题。其实这个模型性能并不算高,选这个主要是因为这个模型结构和之前的很像,方便说明。
1.特征提取
首先,之前所有的模型其实是包含两部分的,一个是各种卷积层,最后是密集连接层(Dense)组成的分类器。前一部分叫卷积基,调用的模型其实就在调取模型的卷积基部分,最后更换自己需要的分类器来进行数据处理。
以这次使用的VGG16模型的结构为例,调用模型时只选取卷积基:
(这个模型算比较大的,如果在程序内下载不方便,可以在网上搜搜VGG16,keras关键词手动放到本地)
from keras.applications import VGG16
conv_base = VGG16(
weights = 'imagenet', #指定模型初始化的权重检查点
include_top = False, #指定模型最后是否包含密集连接分类器(Dense)层,VGG16是1000个类别,本次预测猫狗的话,是2个类别,所以不使用
input_shape = (150,150,3) #输入到网络中的张量形状,这个可以不用输入,能够自动检测
)
conv_base.summary()
![f9e5aad32185351164c8e913506f2e2a.png](https://i-blog.csdnimg.cn/blog_migrate/6c1662eabccb2545c5618fbaf248b2ed.jpeg)
最后的特征形状是(4,4,512),需要在这个特征基础上添加一个密集连接器。接下来又有两种方法选择
1.1不使用数据增强的快速提取
这种方法本质上就是转换自己的数据结构,来满足调用模型输出的结构(4,4,512),从而连接模型。优点是速度快,不适用GPU,节省计算量;缺点是容易过拟合。
#使用预训练的卷积基提取特征,把图片变成模型需要的numpy结构
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_small_dir = 'D:/data/cat-dog/small_samples'
train_dir = os.path.join(base_small_dir, 'train')
validation_dir = os.path.join(base_small_dir, 'validation')
test_dir = os.path.join(base_small_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)
目前提取的特征形状均为(样本数,4,4,512)。如果要输入到密集连接分类器中时,必须转换为形状为(样本数,4*4*512),可以理解成Flatten层的作用。
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.layers import Dense, Dropout
from keras import optimizers
model = models.Sequential()
model.add(Dense(256, activation = 'relu', input_shape = (512*4*4,)))
model.add(Dropout(0.5))
model.add(Dense(1, activation = 'sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=2e-5), metrics=['acc'])
history = model.fit(
train_features, train_labels,
epochs = 30,
batch_size = 20,
validation_data= (validation_features, validation_labels)
)
观察结果:
![3a4d745e4085de02c4b8a0a024c76638.png](https://i-blog.csdnimg.cn/blog_migrate/3cdd9cf22e6c827cdadf0680b6b90f5b.jpeg)
注意y轴,可以看出进度已经逼近90%左右的准确率,自己构造模型的话,在数据增强的加持下,不断微调也只能达到87%左右的准确率。但是这个模型在随机50%dropout的情况下还是过拟合很明显。
1.2使用数据增强的快速提取
这种方法原理就比较简单了,直接使用原始的预训练VGG16模型,然后接入到使用数据处理后的数据集中就好。
注意:
在图像处理的应用上,这种模型的计算量十分大,最好使用keras的GPU模式,反正我是没敢试只有CPU进行处理的情况。
在预训练模型卷积基上直接添加密集连接分类器:
from keras import models
from keras.layers import Dense, Dropout, Flatten
from keras import optimizers
model = models.Sequential()
model.add(conv_base)
model.add(Flatten())
model.add(Dense(256, activation = 'relu'))
model.add(Dense(1, activation = 'sigmoid'))
还有一点,需要在编译前冻结卷积基,防止训练时的结果污染之前的学习结果,因为Dense层是随机初始化的。
conv_base.trainable = False
数据增强和训练模型,和之前猫狗模型的数据增强设置一样,再贴一次代码:
train_datagen = ImageDataGenerator(
rescale = 1/255, #对图片的每个像素值均乘上这个放缩因子,把像素值放缩到0和1之间有利于模型的收敛
rotation_range = 40, #角度值,0-180.表示图像随机旋转的角度范围
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' #因为使用了binary_crossentropy损失函数,所以用二进制标签
)
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
)
训练结果图找不到了,精度大概在90-92%左右。
2.微调模型
模型微调也是广泛使用的模型复用方法。其原理是修改(解冻)调用模型的部分层,让这些层和分类层联合训练。结合上文的知识,之前冻结整个卷积基是防止模型被破坏,现在解冻最后加的层,相当于手动“破坏”模型靠顶部的编码,这些编码是更专业化的特征。至于特征专业化和顶部底部的影响就不展开了,大家可以自己去查找资料。
先看下VGG16的模型结构,共有5个卷积块:
![6bd8137f9bfe52659c03edb07712d145.png](https://i-blog.csdnimg.cn/blog_migrate/a9a279b89a704600b307c15df8fe8580.jpeg)
我们将调整最后一个卷积块的最后3个卷积层,也就是说到block4_pool的所有层都该冻结,后边的层是可进行训练的。
在上面的例子中,所有的卷积层都是冻结的,从这种情况下开始编程:
conv_trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'bloc5_conv1': #这层之后的所有层解冻,包含该层
set_trainable = True
if set_trainable:
layer.trainable = True
else:
layer.trainable = False
之后仍然是画图观察和选择参数(抱歉图又找不到了,代码跟之前的类似)。最后在测试集上评估模型:
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_loss, test_acc)
最后使用了带数据增强的预训练+模型微调的方法,最后的精度能够达到95-97%。这个结果已经达到当时这个数据集比赛的最佳结果之一,而且这还是在我们只使用2000个样本的情况下(样本共有20000个,占总数的10%)。对我们日常的工作来说,很可能无法有较大样本量用于训练,这个方法还是比较有参考意义的。
图像处理类的深度学习应该就告一段落,也许之后有时间会补充一些可视化相关的内容,但个人电脑的显卡不是很给力,到时候视情况而定吧。如果有哪些不太明白的可以在评论或私信联系我。