一.数据集准备
数据集共1400张机场或湖泊的图片,因此此分类为简单的二分类问题,通过CNN对数据集进行模型训练,得出相关指标。
数据集如下:
机场
湖泊
二.读取数据集
- 数据集路径
- 导入相关模块
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pathlib #使用pathlib对路径对象进行管理
import random
- 构造路径对象,获取所有图片路径,并打乱数据集
pic_dir = 'D:/tensorflowDataSet/2_class'
pic_root = pathlib.Path(pic_dir) #构造路径对象
all_image_path = list(pic_root.glob('*/*')) #使用正则表达式获取所有图片路径对象
all_image_path = [str(path) for path in all_image_path] #获取所有图片路径名
random.shuffle(all_image_path) #对数据进行打乱
all_image_path
['D:\\tensorflowDataSet\\2_class\\lake\\lake_272.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_039.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_488.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_342.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_284.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_099.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_522.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_414.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_377.jpg'......
- 根据 2_class目录下的两个分类,构造标签。
dirPath = pic_root.glob('*/') #正则构造2_class目录下子级目录airplane,lake的对象
names = [item.name for item in dirPath]
name_label = dict([(name,label) for label,name in enumerate(names)])
name_label
分类标签
{'airplane': 0, 'lake': 1}
- 将之前所有图片对应到自己的标签上。即找到all_image_path的所有图片对应的标签。
#图片的父目录代表了数据属于那种类型
all_image_parent = [pathlib.Path(image_path).parent.name for image_path in all_image_path]
all_image_label = [name_label[name] for name in all_image_parent]
all_image_path[:6]
all_image_label[:6]
检查是否能对应上
['D:\\tensorflowDataSet\\2_class\\airplane\\airplane_445.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_436.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_084.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_673.jpg',
'D:\\tensorflowDataSet\\2_class\\airplane\\airplane_342.jpg',
'D:\\tensorflowDataSet\\2_class\\lake\\lake_272.jpg']
[0, 0, 1, 0, 0, 1]
三.读取图片数据,并进行预处理。
- 定义加载图片的函数。
def load_pic(path):
image_binary = tf.io.read_file(path) #读取图片,二进制数据
image_tensor = tf.image.decode_jpeg(image_binary,channels=3) #对图片按照指定格式进行解码,彩色图片RGB,channels=3
image_tensor = tf.image.resize(image_tensor,[256,256])
image_tensor = tf.cast(image_tensor,tf.float32)
image_tensor = image_tensor / 255 #对数据进行归一化,建议使用sklearn模块的MinMaxScaler,StandardScaler,能将数据归一化到[0,1],并服从正态分布,加速模型训练。
return image_tensor
plt.imshow(load_pic(all_image_path[1]))
- 对模型构建输入数据
path_dataset = tf.data.Dataset.from_tensor_slices(all_image_path) #读取图片路径dataset
image_dataset = path_dataset.map(load_pic) #加载所有图片
label_dataset = tf.data.Dataset.from_tensor_slices(all_image_label) #读取标签
dataset = tf.data.Dataset.zip((image_dataset,label_dataset)) #将图片及对应标签进行拉链
- 划分训练集,测试集 。
total = len(all_image_path) #图片总数:1400
test_total = int(total * 0.2) #测试集占20%
train_total = total - test_total #训练集占80%
train_ds = dataset.skip(test_total) #跳过20%数据为训练集
test_ds = dataset.take(test_total) #取前20%数据集为测试集
train_ds = train_ds.shuffle(train_total).batch(32) #对训练集进行打乱,设置batch,防止一次性加载数据到内存
test_ds = test_ds.batch(32) #对测试集设置batch,分批次进行训练,防止一次性加载到内存
四.模型创建及训练
model = tf.keras.Sequential() #顺序模型
model.add(tf.keras.layers.Conv2D(64,(3,3),input_shape=(256,256,3),activation='relu')) #卷积层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.Conv2D(64,(3,3),activation='relu')) #卷积层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.MaxPooling2D()) #池化层
model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu')) #卷积层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.Conv2D(128,(3,3),activation='relu')) #卷积层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.MaxPooling2D()) #池化层
model.add(tf.keras.layers.Conv2D(256,(3,3),activation='relu')) #卷积层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.Conv2D(256,(3,3),activation='relu')) #卷积层
model.add(tf.keras.layers.GlobalAveragePooling2D()) #全局平均池化
model.add(tf.keras.layers.Dense(256,activation='relu')) #全连接层
#model.add(tf.keras.layers.BatchNormalization()) #批标准化
model.add(tf.keras.layers.Dense(1,activation='sigmoid')) #输出层
model.summary()
对于如第一个卷积层,为啥Param=1792,首先filter=64,filter的shape为(3,3,3),所以64个filter参数一共为64*3*3*3=1728,加上bias参数64个,共1792,其他层同理可得。
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_18 (Conv2D) (None, 254, 254, 64) 1792
_________________________________________________________________
conv2d_19 (Conv2D) (None, 252, 252, 64) 36928
_________________________________________________________________
max_pooling2d_6 (MaxPooling2 (None, 126, 126, 64) 0
_________________________________________________________________
conv2d_20 (Conv2D) (None, 124, 124, 128) 73856
_________________________________________________________________
conv2d_21 (Conv2D) (None, 122, 122, 128) 147584
_________________________________________________________________
max_pooling2d_7 (MaxPooling2 (None, 61, 61, 128) 0
_________________________________________________________________
conv2d_22 (Conv2D) (None, 59, 59, 256) 295168
_________________________________________________________________
conv2d_23 (Conv2D) (None, 57, 57, 256) 590080
_________________________________________________________________
global_average_pooling2d_1 ( (None, 256) 0
_________________________________________________________________
dense_6 (Dense) (None, 256) 65792
_________________________________________________________________
dense_7 (Dense) (None, 1) 257
=================================================================
- 模型编译及训练
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['acc'])
二分类,loss使用binary_crossentropy
多分类,且标签顺序编码,loss使用sparse_categorical_crossentropy
多分类,且标签使用one-hot,loss使用categorical_crossentropy
record = model.fit(train_ds,
epochs=10, #迭代次数
validation_data=test_ds) #训练模型的同时能看到测试集上的表现
Train for 35 steps, validate for 9 steps
Epoch 1/10
35/35 [==============================] - 392s 11s/step - loss: 0.5093 - acc: 0.7295 - val_loss: 0.2761 - val_acc: 0.9321
Epoch 2/10
35/35 [==============================] - 387s 11s/step - loss: 0.2142 - acc: 0.9411 - val_loss: 0.2173 - val_acc: 0.9750
Epoch 3/10
35/35 [==============================] - 408s 12s/step - loss: 0.1995 - acc: 0.9330 - val_loss: 0.1910 - val_acc: 0.9500
Epoch 4/10
35/35 [==============================] - 423s 12s/step - loss: 0.1380 - acc: 0.9589 - val_loss: 0.1417 - val_acc: 0.9679
Epoch 5/10
35/35 [==============================] - 416s 12s/step - loss: 0.1347 - acc: 0.9598 - val_loss: 0.1154 - val_acc: 0.9679
Epoch 6/10
35/35 [==============================] - 395s 11s/step - loss: 0.1140 - acc: 0.9616 - val_loss: 0.2296 - val_acc: 0.9536
Epoch 7/10
35/35 [==============================] - 392s 11s/step - loss: 0.1465 - acc: 0.9500 - val_loss: 0.0979 - val_acc: 0.9714
Epoch 8/10
35/35 [==============================] - 461s 13s/step - loss: 0.1425 - acc: 0.9589 - val_loss: 0.1694 - val_acc: 0.9714
Epoch 9/10
35/35 [==============================] - 614s 18s/step - loss: 0.1145 - acc: 0.9670 - val_loss: 0.0997 - val_acc: 0.9714
Epoch 10/10
35/35 [==============================] - 634s 18s/step - loss: 0.0975 - acc: 0.9652 - val_loss: 0.1824 - val_acc: 0.9714
- 画图
plt.plot(record.epoch,record.history.get('acc'),label='acc')
plt.plot(record.epoch,record.history.get('val_acc'),label='val_acc')
plt.legend()
由图能看出,在训练集上的表现不是很好,还需提高模型深度,可增加卷积层及卷积核数量。模型没有表现出过拟合,如果产生过拟合可使用Dropout层或正则化参数进行调整。
五.注意事项及总结
- 原始数据通过卷积层的shape,如输入数据为(128,128,3),filter=(3,3),filter个数为64,步长=(1,1),则输出有效区域大小为(126,126,64),其他位置用0填充。可见在池化层通过了下采样达到了压缩数据和参数数量效果。
- 经过卷积后的数据可能参差不齐,在激活函数中可能产生梯度消失及爆炸的情况,可通过批标准化层(tf.keras.layers.BatchNormalization)将卷积后数据标准化,有利于梯度传播。
- CNN同传统神经网络求权值方法类似,使用BP反向传播求解,在池化层,如Max Pooling,使得这个过程不可求导。在这个计算过程中,算法会记录最大值在每个小区域中的位置,在反向传播时,哪个最大值对下一层有贡献,就将残差传递到该最大值的位置。