摘要
本例提取了猫狗大战数据集中的部分数据做数据集,演示tensorflow2.0以上的版本如何使用Keras实现图像分类,分类的模型使用efficientnet。
训练
第一步 导入需要的数据包,设置全局参数
首先导入efficientnet的包,执行命令:pip install efficientnet,然后再就可以导入efficientnet了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | imimport numpy as np from tensorflow.keras.optimizers import Adam import cv2 from tensorflow.keras.preprocessing.image import img_to_array from sklearn.model_selection import train_test_split from tensorflow.python.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau import os import efficientnet.tfkeras as efn from tensorflow_core.python.keras.layers import GlobalAveragePooling2D, Dense from tensorflow_core.python.keras.models import Sequential |
这里可以看出tensorflow2.0以上的版本集成了Keras,我们在使用的时候就不必单独安装Keras了,以前的代码升级到tensorflow2.0以上的版本将keras前面加上tensorflow即可。tensorflow说完了,再说明一下几个重要的全局参数
norm_size = 100 设置输入图像的大小,图像的大小根据自己的需求设置,别太大,够用就行了。
datapath = 'data/train' 设置图片存放的路径,在这里要说明一下如果图片很多,一定不要放在工程目录下,否则Pycharm加载工程的时候会浏览所有的图片,很慢很慢。
EPOCHS = 100 epochs的数量,关于epoch的设置多少合适,这个问题很纠结,一般情况设置300足够了,如果感觉没有训练好,再载入模型训练。
INIT_LR = 1e-3 学习率,一般情况从0.001开始逐渐降低,也别太小了到1e-6就可以了。
classnum = 2 类别数量,数据集有两个类别,所有就分为两类。
batch_size = 16 batchsize,根据硬件的情况和数据集的大小设置,太小了抖的厉害,太大了收敛不好,根据经验来,一般设置为2的次方。
第二步 加载图片
处理图像的步骤:
- 读取图像
- 用指定的大小去resize图像。
- 将图像转为数组
- 图像归一化
- 标签onehot(标签要不要做onehot和选用的loss函数有关,本例选用的loss可以直接处理标签,所以不用onehot)
具体做法详见代码:
labelList = []
dicClass = {'cat': 0, 'dog': 1}
def loadImageData():
imageList = []
listImage = os.listdir(datapath)
for img in listImage:
labelName = dicClass[img.split('.')[0]]
print(labelName)
labelList.append(labelName)
dataImgPath = os.path.join(datapath, img)
print(dataImgPath)
image = cv2.imdecode(np.fromfile(dataImgPath, dtype=np.uint8), -1)
image = cv2.resize(image, (norm_size, norm_size), interpolation=cv2.INTER_LANCZOS4)
image = img_to_array(image)
imageList.append(image)
imageList = np.array(imageList, dtype="int") / 255.0
return imageList
print("开始加载数据")
imageArr = loadImageData()
labelList = np.array(labelList)
print("加载数据完成")
做好数据之后,我们需要切分训练集和测试集,一般按照4:1的比例来切分。切分数据集使用train_test_split()方法,需要导入from sklearn.model_selection import train_test_split 包。例:
trainX, valX, trainY, valY = train_test_split(imageArr, labelList, test_size=0.2, random_state=42)
第三步 图像增强
ImageDataGenerator()是keras.preprocessing.image模块中的图片生成器,同时也可以在batch中对数据进行增强,扩充数据集大小,增强模型的泛化能力。比如进行旋转,变形,归一化等等。
1 | keras.preprocessing.image.ImageDataGenerator(featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsilon=1e-06, rotation_range=0.0, width_shift_range=0.0, height_shift_range=0.0, brightness_range=None, shear_range=0.0, zoom_range=0.0, channel_shift_range=0.0, fill_mode='nearest', cval=0.0, horizontal_flip=False, vertical_flip=False, rescale=None, preprocessing_function=None, data_format=None, validation_split=0.0) |
参数:
- featurewise_center: Boolean. 对输入的图片每个通道减去每个通道对应均值。
- samplewise_center: Boolan. 每张图片减去样本均值, 使得每个样本均值为0。
- featurewise_std_normalization(): Boolean()
- samplewise_std_normalization(): Boolean()
- zca_epsilon(): Default 12-6
- zca_whitening: Boolean. 去除样本之间的相关性
- rotation_range(): 旋转范围
- width_shift_range(): 水平平移范围
- height_shift_range(): 垂直平移范围
- shear_range(): float, 透视变换的范围
- zoom_range(): 缩放范围
- fill_mode: 填充模式, constant, nearest, reflect
- cval: fill_mode == 'constant'的时候填充值
- horizontal_flip(): 水平反转
- vertical_flip(): 垂直翻转
- preprocessing_function(): user提供的处理函数
- data_format(): channels_first或者channels_last
- validation_split(): 多少数据用于验证集
本例使用的图像增强代码如下:
train_datagen = ImageDataGenerator(featurewise_center=True,
featurewise_std_normalization=True,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)
val_datagen = ImageDataGenerator() # 验证集不做图片增强
train_generator = train_datagen.flow(trainX, trainY, batch_size=batch_size, shuffle=True)
val_generator = val_datagen.flow(valX, valY, batch_size=batch_size, shuffle=True)
第四步 保留最好的模型和动态设置学习率
ModelCheckpoint用来保存成绩最好的模型。
语法如下:
keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1)
该回调函数将在每个epoch后保存模型到filepath
filepath可以是格式化的字符串,里面的占位符将会被epoch值和传入on_epoch_end的logs关键字所填入
例如,filepath若为weights.{epoch:02d-{val_loss:.2f}}.hdf5,则会生成对应epoch和验证集loss的多个文件。
参数
- filename:字符串,保存模型的路径
- monitor:需要监视的值
- verbose:信息展示模式,0或1
- save_best_only:当设置为True时,将只保存在验证集上性能最好的模型
- mode:‘auto’,‘min’,‘max’之一,在save_best_only=True时决定性能最佳模型的评判准则,例如,当监测值为val_acc时,模式应为max,当检测值为val_loss时,模式应为min。在auto模式下,评价准则由被监测值的名字自动推断。
- save_weights_only:若设置为True,则只保存模型权重,否则将保存整个模型(包括模型结构,配置信息等)
- period:CheckPoint之间的间隔的epoch数
ReduceLROnPlateau当评价指标不在提升时,减少学习率,语法如下:
keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10, verbose=0, mode='auto', epsilon=0.0001, cooldown=0, min_lr=0)
当学习停滞时,减少2倍或10倍的学习率常常能获得较好的效果。该回调函数检测指标的情况,如果在patience个epoch中看不到模型性能提升,则减少学习率
参数
- monitor:被监测的量
- factor:每次减少学习率的因子,学习率将以lr = lr*factor的形式被减少
- patience:当patience个epoch过去而模型性能不提升时,学习率减少的动作会被触发
- mode:‘auto’,‘min’,‘max’之一,在min模式下,如果检测值触发学习率减少。在max模式下,当检测值不再上升则触发学习率减少。
- epsilon:阈值,用来确定是否进入检测值的“平原区”
- cooldown:学习率减少后,会经过cooldown个epoch才重新进行正常操作
- min_lr:学习率的下限
本例代码如下:
checkpointer = ModelCheckpoint(filepath='weights_best_ efficientnet _model.hdf5',
monitor='val_accuracy', verbose=1, save_best_only=True, mode='max')
reduce = ReduceLROnPlateau(monitor='val_accuracy', patience=10,
verbose=1,
factor=0.5,
min_lr=1e-6)
第五步 建立模型并训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | covn_base = efn.EfficientNetB0(weights='imagenet', include_top=False, input_shape=[norm_size, norm_size, 3]) covn_base.trainable = True # 冻结前面的层,训练最后10层 for layers in covn_base.layers[:-10]: layers.trainable = False # 构建模型 model = Sequential([ covn_base, GlobalAveragePooling2D(), # 加入全局平均池化层 Dense(2, activation='softmax') # 添加输出层(10分类) ]) #打印每层参数信息 model.summary() optimizer = Adam(lr=INIT_LR) #编译模型 model.compile( optimizer=optimizer, #使用adam优化器 loss = 'sparse_categorical_crossentropy', #交叉熵损失函数 metrics=['accuracy'] #评价函数 ) history = model.fit_generator(train_generator, steps_per_epoch=trainX.shape[0] / batch_size, validation_data=val_generator, epochs=EPOCHS, validation_steps=valX.shape[0] / batch_size, callbacks=[checkpointer, reduce], verbose=1, shuffle=True) model.save('my_model_efficientnet.h5') |
第六步 保留训练结果,并将其生成图片
loss_trend_graph_path = r"WW_loss.jpg"
acc_trend_graph_path = r"WW_acc.jpg"
import matplotlib.pyplot as plt
print("Now,we start drawing the loss and acc trends graph...")
# summarize history for accuracy
fig = plt.figure(1)
plt.plot(history.history["accuracy"])
plt.plot(history.history["val_accuracy"])
plt.title("Model accuracy")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(["train", "test"], loc="upper left")
plt.savefig(acc_trend_graph_path)
plt.close(1)
# summarize history for loss
fig = plt.figure(2)
plt.plot(history.history["loss"])
plt.plot(history.history["val_loss"])
plt.title("Model loss")
plt.ylabel("loss")
plt.xlabel("epoch")
plt.legend(["train", "test"], loc="upper left")
plt.savefig(loss_trend_graph_path)
plt.close(2)
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import numpy as np from tensorflow.keras.optimizers import Adam import cv2 from tensorflow.keras.preprocessing.image import img_to_array from sklearn.model_selection import train_test_split from tensorflow.python.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau import os import efficientnet.tfkeras as efn from tensorflow_core.python.keras.layers import GlobalAveragePooling2D, Dense from tensorflow_core.python.keras.models import Sequential norm_size = 100 datapath = 'data/train' EPOCHS = 100 INIT_LR = 1e-3 labelList = [] dicClass = {'cat': 0, 'dog': 1} classnum = 2 batch_size = 16 def loadImageData(): imageList = [] listImage = os.listdir(datapath) for img in listImage: labelName = dicClass[img.split('.')[0]] print(labelName) labelList.append(labelName) dataImgPath = os.path.join(datapath, img) print(dataImgPath) image = cv2.imdecode(np.fromfile(dataImgPath, dtype=np.uint8), -1) image = cv2.resize(image, (norm_size, norm_size), interpolation=cv2.INTER_LANCZOS4) print(image.shape) image = img_to_array(image) imageList.append(image) imageList = np.array(imageList, dtype="int") / 255.0 return imageList print("开始加载数据") imageArr = loadImageData() labelList = np.array(labelList) print("加载数据完成") print(labelList) ''' model = efn.EfficientNetB1(weights=None, classes=classnum,input_shape=[norm_size,norm_size,3]) model.compile(optimizer=optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy']) ''' trainX, valX, trainY, valY = train_test_split(imageArr, labelList, test_size=0.3, random_state=42) from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator(featurewise_center=True, featurewise_std_normalization=True, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True) val_datagen = ImageDataGenerator() # 验证集不做图片增强 train_generator = train_datagen.flow(trainX, trainY, batch_size=batch_size, shuffle=True) val_generator = val_datagen.flow(valX, valY, batch_size=batch_size, shuffle=True) checkpointer = ModelCheckpoint(filepath='weights_best_efficientnet_model.hdf5', monitor='val_accuracy', verbose=1, save_best_only=True, mode='max') reduce = ReduceLROnPlateau(monitor='val_accuracy', patience=10, verbose=1, factor=0.5, min_lr=1e-6) covn_base = efn.EfficientNetB0(weights='imagenet', include_top=False, input_shape=[norm_size, norm_size, 3]) covn_base.trainable = True # 冻结前面的层,训练最后10层 for layers in covn_base.layers[:-10]: layers.trainable = False # 构建模型 model = Sequential([ covn_base, GlobalAveragePooling2D(), # 加入全局平均池化层 Dense(2, activation='softmax') # 添加输出层(10分类) ]) #打印每层参数信息 model.summary() optimizer = Adam(lr=INIT_LR) #编译模型 model.compile( optimizer=optimizer, #使用adam优化器 loss = 'sparse_categorical_crossentropy', #交叉熵损失函数 metrics=['accuracy'] #评价函数 ) history = model.fit_generator(train_generator, steps_per_epoch=trainX.shape[0] / batch_size, validation_data=val_generator, epochs=EPOCHS, validation_steps=valX.shape[0] / batch_size, callbacks=[checkpointer, reduce], verbose=1, shuffle=True) model.save('my_model_efficientnet.h5') print(history) loss_trend_graph_path = r"WW_loss.jpg" acc_trend_graph_path = r"WW_acc.jpg" import matplotlib.pyplot as plt print("Now,we start drawing the loss and acc trends graph...") # summarize history for accuracy fig = plt.figure(1) plt.plot(history.history["accuracy"]) plt.plot(history.history["val_accuracy"]) plt.title("Model accuracy") plt.ylabel("accuracy") plt.xlabel("epoch") plt.legend(["train", "test"], loc="upper left") plt.savefig(acc_trend_graph_path) plt.close(1) # summarize history for loss fig = plt.figure(2) plt.plot(history.history["loss"]) plt.plot(history.history["val_loss"]) plt.title("Model loss") plt.ylabel("loss") plt.xlabel("epoch") plt.legend(["train", "test"], loc="upper left") plt.savefig(loss_trend_graph_path) plt.close(2) print("We are done, everything seems OK...") # #windows系统设置10关机 os.system("shutdown -s -t 10") |