一.数据集准备
数据集共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,使得这个过程不可求导。在这个计算过程中,算法会记录最大值在每个小区域中的位置,在反向传播时,哪个最大值对下一层有贡献,就将残差传递到该最大值的位置。