Keras:VGG16模型微调

一、介绍
当我们的数据集较小,只有几百几千张图片的时候,我们很难在一个新的网络结构上训练出具有很高准确率的模型,为此我们需要借助预训练网络模型(即已经训练好的网络模型,如VGG16)。我们利用自己的数据集来重新训练这些模型的分类层,就可以获得比较高的准确率。

目前大部分的卷积神经网络都分为两部分,第一部分由卷积层、池化层组成的卷积基部分,主要用于特征提取;第二部分是由全连接神经网络组成的分类器,主要用于图像分类。当然,目前大部分网络逐渐使用GlobalAveragePooling代替全连接层进行分类,但我们本文对此不进行讨论。

微调主要包括五个部分:

  1. 在预训练网络模型加载自定义网络结构。我们加载VGG16时,只会加载卷积基部分,抛弃分类器,同时我们会在加载的网络模型上面添加我们自己的分类器。
  2. 冻结预训练网络。我们必须将预训练网络模型设置为trainable=False,我们在训练自己的分类器时,是不能将预训练网络一起进行训练的,否则就会破坏预训练网络的原始参数。
  3. 训练自己的网络,之后我们就会使用数据集来训练自己添加的网络部分。训练完成之后,就将模型进行保存。
  4. 加载第三部分训练好的模型,将模型预训练网络的部分网络进行解冻。
    再次使用我们的数据集对解冻的网络部分进行训练。保存模型

二、微调的前三个部分

我使用GlobalAveragePooling代替全连接层,并使用BatchNormalization代替Dropout,反而获得了更低的准确率,所以最后仍然使用了全连接层进行分类试验。
vgg16_keras.py

# -*- coding: utf-8 -*-
"""
Created on Wed Jul 22 15:11:47 2020

@author: zqq
"""

import json
from keras.applications import VGG16 #导入VGG16,首次导入会进行下载,下载速度太慢就去我提供的连接进行下载
from keras import layers
from keras import models
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras import losses
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import ModelCheckpoint
import os
 
train_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\train'  #训练数据集
valid_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\test'    #验证数据集
 
#加载VGG16模型
conv_base = VGG16(weights='imagenet',        #使用预训练数据
                  include_top=False,         #不使用原来的分类器
                  input_shape=(150, 150, 3)) #将输入尺寸调整为(150, 150, 3)
 
#添加自己的网络模型
model = models.Sequential()             
model.add(conv_base)                #首先添加我们加载的VGG16模型
model.add(layers.Flatten())         #添加全连接神经网络,即分类器
model.add(layers.Dense(units=512))
model.add(layers.Dropout(0.5))      #使用Dropout,50%失活
model.add(layers.Dense(units=2, activation='softmax'))  #最终分类为2
 
#以文本显示模型超参数
model.summary()  
#print(model.summary())  
 
conv_base.trainable = False  #第一步,设置网络模型不可训练,冻层操作
 
#对训练数据使用数据增强
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')
#验证数据不能使用模型增强
tesst_datagen = ImageDataGenerator(rescale=1./255)
 
#训练数据导入
train_generator = train_datagen.flow_from_directory(directory=train_dir,
                                                    target_size=(150, 150),
                                                    batch_size=20,
                                                    class_mode='categorical')
#验证数据导入
validation_generator = tesst_datagen.flow_from_directory(directory=valid_dir,
                                                         target_size=(150, 150),
                                                         batch_size=20,
                                                         class_mode='categorical')
 
#对网络模型进行配置
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss=losses.binary_crossentropy,
              metrics=['acc'])
 
# Checkpoint
# 将训练好的模型进行保存
#model.save('VGG16_Base.h5')
path = "save_models"
if not os.path.exists(path):
    os.makedirs(path)

save_name = "model"
filepath = "save_models/vgg16_finetune_" + str(save_name) + "_-{epoch:03d}-{val_acc:.3f}.h5"
checkpoint = ModelCheckpoint(filepath=filepath, monitor="val_acc", verbose=1, save_best_only=True, mode="max")

#开始训练网络模型
history = model.fit_generator(generator=train_generator,
                              steps_per_epoch=1, # 每一轮中的epoch
                              epochs=2, # 进行20轮训练
                              validation_data=validation_generator,
                              validation_steps=50,
                              callbacks=[checkpoint, ]) 
#显示训练与验证过程的曲线
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

print('acc:',history.history['acc'][-1])

json_data = {}
json_data['acc'] = acc
json_data['val_acc'] = val_acc
json_data['loss'] = loss
json_data['val_loss'] = val_loss

path = "history_json"
if not os.path.exists(path):
    os.makedirs(path)

with open(f'history_json/vgg16_finetune_{save_name}.json', 'w') as f:
    json.dump(json_data, f)    

三、微调模型的后两步

后两步跟前三步是差不多的步骤,只不过加载的模型不再是VGG16而是前面我们刚刚训练好的模型。同时,不再对所有网络模型进行冻结,而是解冻靠近分类器的几层网络模型。然后使用同样的数据对解冻的部分与之前的分类器部分重新进行训练。

vgg16_keras_next.py

# -*- coding: utf-8 -*-
"""
Created on Thu Jul 23 08:34:56 2020

@author: zqq
"""

import json
import os
from keras.models import load_model
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras import losses
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import ModelCheckpoint
 
#数据集的地址
train_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\train'  #训练数据集
valid_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\test'    #验证数据集
 
#加载上面训练好的模型
conv_base = load_model('C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\code\\save_models\\vgg16_model_-001-0.875.h5')
 
#解冻所有网络
conv_base.trainable = True
 
#对block_conv1之前的网络进行再次冻结,而之后的网络依然保存解冻状态
set_trainiable = False
 
for layer in conv_base.layers:
    if layer.name == 'block_conv1':
        set_trainiable = True
    if set_trainiable:
        layer.trainiable = True
    else:
        layer.trainiable = False
 
model = conv_base
 
#训练使用数据增强
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')
#验证数据不可能使用数据增强
tesst_datagen = ImageDataGenerator(rescale=1./255)
 
train_generator = train_datagen.flow_from_directory(directory=train_dir,
                                                    target_size=(150, 150),
                                                    batch_size=20,
                                                    class_mode='categorical')
validation_generator = tesst_datagen.flow_from_directory(directory=valid_dir,
                                                         target_size=(150, 150),
                                                         batch_size=20,
                                                         class_mode='categorical')
#配置网络
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss=losses.binary_crossentropy,
              metrics=['acc'])

path = "save_models"
if not os.path.exists(path):
    os.makedirs(path)
save_name = "model"
filepath = "save_models/vgg16_finetune" + str(save_name) + "_-{epoch:03d}-{val_acc:.3f}.h5"
checkpoint = ModelCheckpoint(filepath=filepath, monitor="val_acc", verbose=1, save_best_only=True, mode="max")

#进行训练
history = model.fit_generator(generator=train_generator,
                              steps_per_epoch=1, # 每一轮中的epoch,可以自己调
                              epochs=2, # 进行20轮训练,可以自调
                              validation_data=validation_generator,
                              validation_steps=50,
                              callbacks=[checkpoint, ])
#微调之后的网络模型进行保存
#model.save('VGG16_Base_FT.h5')
 
#显示训练与验证过程的曲线
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

print('acc:',history.history['acc'][-1])

json_data = {}
json_data['acc'] = acc
json_data['val_acc'] = val_acc
json_data['loss'] = loss
json_data['val_loss'] = val_loss

path = "history_json"
if not os.path.exists(path):
    os.makedirs(path)

with open(f'history_json/vgg16_finetune_{save_name}.json', 'w') as f:
    json.dump(json_data, f)  

附1:
加载json数据,绘制acc和loss

# -*- coding: utf-8 -*-
"""
Created on Wed Jul 22 17:27:45 2020

@author: zqq
"""

import matplotlib.pyplot as plt
import json

path = 'C:/Users/zqq/Desktop/MyProject/FineTune/code/history_json/vgg16_finetune_model.json'

with open(path) as f:
    json_data = json.load(f)

acc = json_data['acc']
val_acc = json_data['val_acc']
loss = json_data['loss']
val_loss = json_data['val_loss']


epochs = range(1, len(acc)+1)

plt.xlabel("epoch")
plt.ylabel("acc") 
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.legend()
 
plt.figure()
plt.xlabel("epoch")
plt.ylabel("loss") 
plt.plot(epochs, loss, 'bo', label='Training acc')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.legend()
 
plt.show()

附2
整合代码

# -*- coding: utf-8 -*-
"""
Created on Thu Jul 23 09:28:40 2020

@author: zqq
"""

import json
from keras.applications import VGG16 #导入VGG16,首次导入会进行下载,下载速度太慢就去我提供的连接进行下载
from keras import layers
from keras import models
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras import losses
import matplotlib.pyplot as plt
from tensorflow.keras.callbacks import ModelCheckpoint
import os
from keras.models import load_model


train_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\train'  #训练数据集
valid_dir = 'C:\\Users\\zqq\\Desktop\\MyProject\\FineTune\\dataset\\valid'    #验证数据集

#加载VGG16模型
conv_base = VGG16(weights='imagenet',        #使用预训练数据
                  include_top=False,         #不使用原来的分类器
                  input_shape=(150, 150, 3)) #将输入尺寸调整为(150, 150, 3)

#添加自己的网络模型
model = models.Sequential()             
model.add(conv_base)                #首先添加我们加载的VGG16模型
model.add(layers.Flatten())         #添加全连接神经网络,即分类器
model.add(layers.Dense(units=512))
model.add(layers.Dropout(0.5))      #使用Dropout,50%失活
model.add(layers.Dense(units=2, activation='softmax'))  #最终分类为2

#以文本显示模型超参数
model.summary()  
#print(model.summary())  
 
conv_base.trainable = False  #第一步,设置网络模型不可训练,冻层操作
 
#对训练数据使用数据增强
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')
#验证数据不能使用模型增强
valid_datagen = ImageDataGenerator(rescale=1./255)
 
#训练数据导入
train_generator = train_datagen.flow_from_directory(directory=train_dir,
                                                    target_size=(150, 150),
                                                    batch_size=20,
                                                    class_mode='categorical')
#验证数据导入
validation_generator = valid_datagen.flow_from_directory(directory=valid_dir,
                                                         target_size=(150, 150),
                                                         batch_size=20,
                                                         class_mode='categorical')
 
#对网络模型进行配置
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss=losses.binary_crossentropy,
              metrics=['acc'])
 
# Checkpoint
# 将训练好的模型进行保存
#model.save('VGG16_Base.h5')
path = "save_models"
if not os.path.exists(path):
    os.makedirs(path)
save_name = "model"
filepath = "save_models/vgg16_" + str(save_name) + "_-{epoch:03d}-{val_acc:.3f}.h5"
checkpoint = ModelCheckpoint(filepath=filepath, monitor="val_acc", verbose=1, save_best_only=True, mode="max")
#开始训练网络模型
history = model.fit_generator(generator=train_generator,
                              steps_per_epoch=1, # 每一轮中的epoch,可以自己调
                              epochs=2, # 进行20轮训练,可以自调
                              validation_data=validation_generator,
                              validation_steps=50,
                              callbacks=[checkpoint, ]) 

#显示训练与验证过程的曲线
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

print('acc:',history.history['acc'][-1])

json_data = {}
json_data['acc'] = acc
json_data['val_acc'] = val_acc
json_data['loss'] = loss
json_data['val_loss'] = val_loss

# 创建history_json文件夹将数据保存
path = "history_json"
if not os.path.exists(path):
    os.makedirs(path)

with open(f'history_json/vgg16_{save_name}.json', 'w') as f:
    json.dump(json_data, f)  



#重新训练上面训练好的模型
conv_base = model

#解冻所有网络
conv_base.trainable = True

set_trainiable = False
 
for layer in conv_base.layers:
    if layer.name == 'block_conv1':
        set_trainiable = True
    if set_trainiable:
        layer.trainiable = True
    else:
        layer.trainiable = False
 
model = conv_base
#配置网络
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss=losses.binary_crossentropy,
              metrics=['acc'])
#进行训练
history = model.fit_generator(generator=train_generator,
                              steps_per_epoch=1, # 每一轮中的epoch,可以自己调
                              epochs=2, # 进行20轮训练,可以自调
                              validation_data=validation_generator,
                              validation_steps=50,
                              callbacks=[checkpoint, ])

#显示训练与验证过程的曲线
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

print('acc:',history.history['acc'][-1])

json_data = {}
json_data['acc'] = acc
json_data['val_acc'] = val_acc
json_data['loss'] = loss
json_data['val_loss'] = val_loss

with open(f'history_json/vgg16_finetune_{save_name}.json', 'w') as f:
    json.dump(json_data, f) 
    

附3
数据说明

dataset文件夹下分为train和valid,
train和valid文件夹下面又分不同类别的图片。

在这里插入图片描述

参考和引用:

https://blog.csdn.net/lpp5406813053/article/details/104376682

仅用来个人学习和分享,如若侵权,留言立删。

尊重他人知识产权,不做拿来主义者!

喜欢的可以关注我哦QAQ,

你的关注和喜欢就是我write博文的动力。

猜你喜欢

转载自blog.csdn.net/AugustMe/article/details/107513820