基于InceptionV3深度学习实现岩石图像智能识别与分类
基于InceptionV3深度学习实现岩石图像智能识别与分类
总体流程
- 整理数据集(训练集、验证集),按照目录格式分类
- 读取数据集图像,归一化处理和数据增强
- 加载预训练模型
InceptionV3
,作为基础模型 - 在Inception卷积神经网络的瓶颈层后设计适用于本项目的网络结构,成为my_mode
- 冻结预训练模型的所有层,变为不可训练,便于正确获得瓶颈层输出的特征,自己添加的层需要训练。相当于把InceptionV3变为一个特征提取器
- 编译、训练、保存
- 预测:读取需识别的图像,转换数据格式,预测输出
数据预处理
使用一个ImageDataGenerator
图片生成器,定义图片处理以及数据增强相关
ImageDataGenerator
,这个API提供数据处理相关功能,以及数据增强功能,使得数据多样化
datagen = ImageDataGenerator(rescale=1. / 255, # 归一化
zoom_range=0.2,
rotation_range=40.,
channel_shift_range=25.,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
# fill_mode:‘constant’,‘nearest’,‘reflect’或‘wrap’之一,
# 当进行变换时超出边界的点将根据本参数给定的方法进行处理
参数(参考:http://www.51zixue.net/Keras/853.html)
- featurewise_center:布尔值,使输入数据集去中心化(均值为0), 按feature执行
- samplewise_center:布尔值,使输入数据的每个样本均值为0
- featurewise_std_normalization:布尔值,将输入除以数据集的标准差以完成标准化, 按feature执行
- samplewise_std_normalization:布尔值,将输入的每个样本除以其自身的标准差
- zca_whitening:布尔值,对输入数据施加ZCA白
- zca_epsilon: ZCA使用的eposilon,默认1e-6
- rotation_range:整数,数据提升时图片随机转动的角度
- width_shift_range:浮点数,图片宽度的某个比例,数据提升时图片水平偏移的幅度
- height_shift_range:浮点数,图片高度的某个比例,数据提升时图片竖直偏移的幅度
- shear_range:浮点数,剪切强度(逆时针方向的剪切变换角度)
- zoom_range:浮点数或形如[lower,upper]的列表,随机缩放的幅度,若为浮点数,则相当于[lower,upper] = [1 - zoom_range, 1+zoom_range]
- channel_shift_range:浮点数,随机通道偏移的幅度
- fill_mode:;‘constant’,‘nearest’,‘reflect’或‘wrap’之一,当进行变换时超出边界的点将根据本参数给定的方法进行处理
- cval:浮点数或整数,当fill_mode=constant时,指定要向超出边界的点填充的值
- horizontal_flip:布尔值,进行随机水平翻转
- vertical_flip:布尔值,进行随机竖直翻转
- rescale: 重放缩因子,默认为None. 如果为None或0则不进行放缩,否则会将该数值乘到数据上(在应用其他变换之前)
ImageDataGenerator.flow_from_directory()
,实现了自动给固定格式目录下的数据集打标签,分批无序读取,返回张量类型数据集。- 这个API要求有严格的目录格式,如下:
data/
train/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
validation/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
generator = datagen.flow_from_directory(
dir_path, # 数据存放路径
target_size=(img_row, img_col), # 目标形状
batch_size=batch_size, # 批数量大小
class_mode='categorical', # 二分类使用binary
# "categorical" :2D one-hot encoded labels
# "binary" will be 1D binary labels
shuffle=is_train # 是否打乱
)
参数:(参考:https://blog.csdn.net/mieleizhi0522/article/details/82191331)
directory: 目标文件夹路径,对于每一个类,该文件夹都要包含一个子文件夹.子文件夹中任何JPG、PNG、BNP、PPM的图片都会被生成器使用.详情请查看此脚本
target_size: 整数tuple,默认为(256, 256). 图像将被resize成该尺寸
color_mode: 颜色模式,为”grayscale”,”rgb”之一,默认为”rgb”.代表这些图片是否会被转换为单通道或三通道的图片.
classes: 可选参数,为子文件夹的列表,如[‘dogs’,’cats’]默认为None. 若未提供,则该类别列表将从directory下的子文件夹名称/结构自动推断。每一个子文件夹都会被认为是一个新的类。(类别的顺序将按照字母表顺序映射到标签值)。通过属性class_indices可获得文件夹名与类的序号的对应字典。
class_mode: “categorical”, “binary”, “sparse”或None之一. 默认为”categorical. 该参数决定了返回的标签数组的形式, “categorical”会返回2D的one-hot编码标签,”binary”返回1D的二值标签.”sparse”返回1D的整数标签,如果为None则不返回任何标签, 生成器将仅仅生成batch数据, 这种情况在使用model.predict_generator()和model.evaluate_generator()等函数时会用到.
batch_size: batch数据的大小,默认32
shuffle: 是否打乱数据,默认为True
seed: 可选参数,打乱数据和进行变换时的随机数种子
save_to_dir: None或字符串,该参数能让你将提升后的图片保存起来,用以可视化
save_prefix:字符串,保存提升后图片时使用的前缀, 仅当设置了save_to_dir时生效
save_format:”png”或”jpeg”之一,指定保存图片的数据格式,默认”jpeg”
flollow_links: 是否访问子文件夹中的软链接
构建InceptionV3模型
Bottleneck feature即为瓶颈层输出的特征,后面的层被丢弃。我们在瓶颈层后添加全连接层进行分类,输出变为符合概率分布的概率。添加的层结构如下。
设置整体卷积神经网络的输入为(150,150,3)矩阵。
自建层输入的是3 * 3 * 2048 的张量,因为我们是分类5类图像,所以最后一层用5个神经元,使用softmax激活函数,输出五种类别各自的概率值。
Flatten:Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。
Dropout:做分类的时候,Dropout 层一般加在全连接层 防止过拟合,提升模型泛化能力。而很少见到卷积层后接Dropout(原因主要是 卷积参数少,不易过拟合)
冻结源模型的所有层,针对数据集大小有三种不同方案:
- 数据集少的就冻住所有的特征提取层
- 数据集中的可以冻住开始一部分的特征提取层
- 数据集多的可以自行训练参数
可视化神经网络结构:
from tensorflow.keras.utils import plot_model
plot_model(model, to_file='inceptionV3_model.png',
show_shapes=True)
def InceptionV3_model(self, lr=0.005, decay=1e-6, momentum=0.9, nb_classes=2,
img_rows=197, img_cols=197, RGB=True, is_plot_model=False):
"""InceptionV3模型,建立自己的模型
Args:
lr (float, optional): 学习率. Defaults to 0.005.
decay ([type], optional): 学习衰减率. Defaults to 1e-6.
momentum (float, optional): Defaults to 0.9.
nb_classes (int, optional): 分类数. Defaults to 2.
img_rows (int, optional): 图片行数. Defaults to 197.
img_cols (int, optional): 图片列数. Defaults to 197.
RGB (bool, optional): 是否为3通道图片. Defaults to True.
is_plot_model (bool, optional): 是否画出模型网络结构图. Defaults to False.
Returns:
[type]: 返回模型
"""
color = 3 if RGB else 1
# 假设最后一层CNN的层输出为(img_rows, img_cols, color
base_model = InceptionV3(weights='imagenet',
include_top=False,
input_shape=(img_rows, img_cols, color),
)
# 对我们的输出进行平铺操作,為全連接层做准备
x = layers.Flatten()(base_model.output)
# 增加一个全连接层,并使用relu作为激活函数,这是需要训练的
x = layers.Dense(1024, activation='relu')(x)
# 添加随机失活,抑制过拟合
x = layers.Dropout(0.2)(x)
# ,输出层,把输出设置成softmax函数
predictions = layers.Dense(nb_classes, activation='softmax')(x)
# 训练模型
model = Model(inputs=base_model.input, outputs=predictions)
# 冻结base_model所有层,这样就可以正确获得bottleneck特征
for layer in base_model.layers:
layer.trainable = False
sgd = SGD(lr=lr, decay=decay, momentum=momentum, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd, metrics=['acc'])
model.summary()
# 可视化网络结构,生成图片
if is_plot_model:
plot_model(model, to_file='inceptionV3_model.png',
show_shapes=True)
return model
训练、保存模型
训练:用fit_generator函数,它可以避免了一次性加载大量的数据,并且生成器与模型将并行执行以提高效率。比如可以在CPU上进行实时的数据提升,同时在GPU上进行模型训练
def train_model(self, model, epochs, train_generator, steps_per_epoch,
validation_generator, validation_steps,path_save_model, is_load_model=False):
"""训练模型,载入、保存、断点续训
Args:
model ([type]): 模型
epochs ([type]): 训练次数
train_generator ([type]): 训练集
steps_per_epoch ([type]):
validation_generator ([type]): 验证集
validation_steps ([type]):
path_save_model ([type]): 保存模型路径
is_load_model (bool, optional): 是否载入模型. Defaults to False.
Returns:
[type]: 训练记录
"""
# 载入模型
if is_load_model and os.path.exists(path_save_model):
print('================载入已训练模型===============')
model = load_model(path_save_model)
# 使用tensorboard
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(
log_dir=log_dir, histogram_freq=1)
# 断点续训
# 格式化字符,防止文件名冲突
checkpoint_path = 'ckpt/transfer_{epoch:02d}-{val_acc:.2f}.h5'
checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
checkpoint_path, # 保存路径,指定为tensorflow二进制文件ckpt
monitor='val_acc', # 监测指标,这里是测试集的acc
save_best_only=False, # 是否只保存最佳
save_weights_only=True, # 只保存权重
mode='max',
period=1 # 每period个周期保存一次
)
# 训练
print('================开始训练================')
history_ft = model.fit_generator(
train_generator,
steps_per_epoch=steps_per_epoch,
epochs=epochs,
validation_data=validation_generator,
validation_steps=validation_steps,
verbose=1, # 日志显示,0/1,1表示输出进度条日志信息
callbacks=[tensorboard_callback, checkpoint_callback])
# 模型保存
print('================保存模型================')
model.save(path_save_model, overwrite=True)
return history_ft
断点续训:在模型训练时保存检查点,防止因意外情况丢失训练进度。
# 断点续训
# 格式化字符,防止文件名冲突
checkpoint_path = 'ckpt/transfer_{epoch:02d}-{val_acc:.2f}.h5'
checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
checkpoint_path, # 保存路径,指定为tensorflow二进制文件ckpt
monitor='val_acc', # 监测指标,这里是测试集的acc
save_best_only=False, # 是否只保存最佳
save_weights_only=True, # 只保存权重
mode='max',
period=1 # 每period个周期保存一次
)
保存、载入模型的详细用法,请参考我的另一篇博客:https://blog.csdn.net/jun_zhong866810/article/details/119708120?spm=1001.2014.3001.5501
可视化acc/loss图
def plot_training(self, history):
"""可视化acc/loss图
Args:
history ([type]): 训练
"""
print('================绘制acc/loss图================')
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, 'b-')
plt.plot(epochs, val_acc, 'r')
plt.title('Training and validation accuracy')
plt.figure()
plt.plot(epochs, loss, 'b-')
plt.plot(epochs, val_loss, 'r-')
plt.title('Training and validation loss')
plt.show()
预测
- 载入我们训练好的模型(读取全部模型或者读取权重)
- 输入待识别岩石图像,图像格式标准化
- 预测并输出对于的岩石标签
# 建立标签字典,便于输出结果
label_dict = {
'0': '安山岩',
'1': '石灰岩',
'2': '石英岩',
'3': '砾岩',
'4': '花岗岩'
}
def loadModel():
"""读取全部模型数据"""
model = tf.keras.models.load_model('model/my_saved_InceptionV3_model.h5')
return model
if __name__ == '__main__':
model = loadModel()
print(model.summary())
for img_name in os.listdir(path):
img_path = path+img_name
img = image.load_img(img_path, target_size=(150, 150))
# 保持输入格式一致
x = image.img_to_array(img) / 255
# 变为四维数据
x = np.expand_dims(x, axis=0)
# 预测
result = model.predict(x)
# 返回最大概率值的索引,类型是张量
index = tf.argmax(result, axis=1)
print(img_name, '======================>', label_dict[str(int(index))])
原始图像:
源代码与数据集
数据集:
链接:https://pan.baidu.com/s/15ZfB79YGxdMZwT4I3OPRiQ
提取码:zjsg
train.py
# -*- coding: utf-8 -*-
# @Time : 2021/08/15
# @Author : Z.J
# @File : train.py
# @Software: vs code
# -*- coding: utf-8 -*-
import os
import datetime
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import layers
from tensorflow.keras.utils import plot_model
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.regularizers import l2
class PowerTransferMode:
"""迁移学习类
"""
def DataGen(self, dir_path, img_row, img_col, batch_size, is_train):
"""读取数据集,并进行数据增强和打标签
Args:
dir_path (str)): 数据集路径
img_row (int): 行数
img_col (int): 行数
batch_size (int): 批数量
is_train (bool): 是否为训练集
Returns:
[type]: 数据集
"""
if is_train:
# ImageDataGenerator :生产图片的批次张量值并且提供数据增强功能
print('==================读取训练数据================')
datagen = ImageDataGenerator(rescale=1. / 255,
zoom_range=0.2,
rotation_range=40.,
channel_shift_range=25.,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
fill_mode='nearest') # fill_mode:‘constant’,‘nearest’,‘reflect’或‘wrap’之一,
# 当进行变换时超出边界的点将根据本参数给定的方法进行处理
else:
# 验证集不需要数据增强
print('==================读取验证数据================')
datagen = ImageDataGenerator(rescale=1. / 255)
generator = datagen.flow_from_directory(
dir_path, # 数据存放路径
target_size=(img_row, img_col), # 目标形状
batch_size=batch_size, # 批数量大小
class_mode='categorical', # 二分类使用binary
# "categorical" :2D one-hot encoded labels
# "binary" will be 1D binary labels
shuffle=is_train # 是否打乱
)
return generator
# InceptionV3模型
def InceptionV3_model(self, lr=0.005, decay=1e-6, momentum=0.9, nb_classes=2,
img_rows=197, img_cols=197, RGB=True, is_plot_model=False):
"""InceptionV3模型,建立自己的模型
Args:
lr (float, optional): 学习率. Defaults to 0.005.
decay ([type], optional): 学习衰减率. Defaults to 1e-6.
momentum (float, optional): Defaults to 0.9.
nb_classes (int, optional): 分类数. Defaults to 2.
img_rows (int, optional): 图片行数. Defaults to 197.
img_cols (int, optional): 图片列数. Defaults to 197.
RGB (bool, optional): 是否为3通道图片. Defaults to True.
is_plot_model (bool, optional): 是否画出模型网络结构图. Defaults to False.
Returns:
[type]: 返回模型
"""
color = 3 if RGB else 1
# 假设最后一层CNN的层输出为(img_rows, img_cols, color)
print('=================加载预训练模型=================')
base_model = InceptionV3(weights='imagenet',
include_top=False,
input_shape=(img_rows, img_cols, color),
)
# 对我们的输出进行平铺操作,為全連接层做准备
x = layers.Flatten()(base_model.output)
# 增加一个全连接层,并使用relu作为激活函数,这是需要训练的
x = layers.Dense(1024, activation='relu')(x)
# 添加随机失活,抑制过拟合
x = layers.Dropout(0.2)(x)
# ,输出层,把输出设置成softmax函数
predictions = layers.Dense(nb_classes, activation='softmax')(x)
# 训练模型
print('================创建自己的模型==================')
model = Model(inputs=base_model.input, outputs=predictions)
# 冻结base_model所有层,这样就可以正确获得bottleneck特征
for layer in base_model.layers:
layer.trainable = False
sgd = SGD(lr=lr, decay=decay, momentum=momentum, nesterov=True)
print('================编译模型=================')
model.compile(loss='categorical_crossentropy',
optimizer=sgd, metrics=['acc'])
print('=================打印模型结构信息=================')
model.summary()
# 可视化网络结构,生成图片
if is_plot_model:
plot_model(model, to_file='inceptionV3_model.png',
show_shapes=True)
return model
def train_model(self, model, epochs, train_generator, steps_per_epoch,
validation_generator, validation_steps,path_save_model, is_load_model=False):
"""训练模型,载入、保存、断点续训
Args:
model ([type]): 模型
epochs ([type]): 训练次数
train_generator ([type]): 训练集
steps_per_epoch ([type]):
validation_generator ([type]): 验证集
validation_steps ([type]):
path_save_model ([type]): 保存模型路径
is_load_model (bool, optional): 是否载入模型. Defaults to False.
Returns:
[type]: 训练记录
"""
# 载入模型
if is_load_model and os.path.exists(path_save_model):
print('================载入已训练模型===============')
model = load_model(path_save_model)
# 使用tensorboard
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(
log_dir=log_dir, histogram_freq=1)
# 断点续训
# 格式化字符,防止文件名冲突
checkpoint_path = 'ckpt/transfer_{epoch:02d}-{val_acc:.2f}.h5'
checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
checkpoint_path, # 保存路径,指定为tensorflow二进制文件ckpt
monitor='val_acc', # 监测指标,这里是测试集的acc
save_best_only=False, # 是否只保存最佳
save_weights_only=True, # 只保存权重
mode='max',
period=1 # 每period个周期保存一次
)
# 训练
print('================开始训练================')
history_ft = model.fit_generator(
train_generator,
steps_per_epoch=steps_per_epoch,
epochs=epochs,
validation_data=validation_generator,
validation_steps=validation_steps,
verbose=1, # 日志显示,0/1,1表示输出进度条日志信息
callbacks=[tensorboard_callback, checkpoint_callback])
# 模型保存
print('================保存模型================')
model.save(path_save_model, overwrite=True)
return history_ft
def plot_training(self, history):
"""可视化acc/loss图
Args:
history ([type]): 训练
"""
print('================绘制acc/loss图================')
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, 'b-')
plt.plot(epochs, val_acc, 'r')
plt.title('Training and validation accuracy')
plt.figure()
plt.plot(epochs, loss, 'b-')
plt.plot(epochs, val_loss, 'r-')
plt.title('Training and validation loss')
plt.show()
if __name__ == '__main__':
image_size = 150
batch_size = 20
num_train = 300
num_val = 100
transfer_model = PowerTransferMode()
# 得到数据
train_generator = transfer_model.DataGen('./data/岩石数据集/train',
image_size,
image_size,
batch_size,
True)
validation_generator = transfer_model.DataGen('./data/岩石数据集/validation',
image_size,
image_size,
batch_size,
False)
# InceptionV3
model = transfer_model.InceptionV3_model(nb_classes=5,
img_rows=image_size,
img_cols=image_size,
is_plot_model=False)
# 训练模型
history_ft = transfer_model.train_model(model,
epochs=50,
train_generator=train_generator,
steps_per_epoch=num_train / batch_size,
validation_generator=validation_generator,
validation_steps=num_val / batch_size,
path_save_model='model/my_saved_InceptionV3_model.h5',
is_load_model=True)
transfer_model.plot_training(history_ft)
predict.py
# -*- coding: utf-8 -*-
# @Time : 2021/08/15
# @Author : Z.J
# @File : predict.py
# @Software: vs code
from tensorflow.keras.preprocessing import image
import numpy as np
import os
import tensorflow as tf
import train
from tensorflow.python.training.checkpoint_management import latest_checkpoint
path = "./tmp/predict/" # 预测图片的路径
path_save_model = './model/my_saved_InceptionV3_model.h5' # 保存的模型的路径
checkpoint_path = 'ckpt/transfer_{epoch:02d}-{val_acc:.2f}.h5' # 检查点路径
checkpoint_root = os.path.dirname(checkpoint_path) # 检查点文件根目录
image_size = 150 # 图片格式(150,150)
# 建立标签字典,便于输出结果
label_dict = {
'0': '安山岩',
'1': '石灰岩',
'2': '石英岩',
'3': '砾岩',
'4': '花岗岩'
}
def loadWeights():
"""
读取保存的权重数据,需先构建网络结构一致的新模型
"""
base_model = train.PowerTransferMode()
model = base_model.InceptionV3_model(
nb_classes=5,
img_rows=image_size,
img_cols=image_size,
is_plot_model=False
)
# 从检查点恢复权重
saved_weights = './ckpt/transfer_50-1.00.h5'
# latest_weights = tf.train.latest_checkpoint(checkpoint_root) 只对ckpt格式文件有用!
model.load_weights(saved_weights)
return model
def loadModel():
"""读取全部模型数据"""
model = tf.keras.models.load_model('model/my_saved_InceptionV3_model.h5')
return model
if __name__ == '__main__':
model = loadWeights()
print(model.summary())
for img_name in os.listdir(path):
img_path = path+img_name
img = image.load_img(img_path, target_size=(150, 150))
# 保持输入格式一致
x = image.img_to_array(img) / 255
# 变为四维数据
x = np.expand_dims(x, axis=0)
# 预测
result = model.predict(x)
# 返回最大概率值的索引,类型是张量
index = tf.argmax(result, axis=1)
print(img_name, '======================>', label_dict[str(int(index))])
参考
https://blog.csdn.net/pengdali/article/details/79050662
https://blog.csdn.net/m0_46334316/article/details/117607628 感谢博主提供的岩石数据集
https://blog.csdn.net/weixin_43999137/article/details/104093095
http://www.51zixue.net/Keras/853.html
https://blog.csdn.net/mieleizhi0522/article/details/82191331