一.数据集准备

数据集共1400张机场或湖泊的图片,因此此分类为简单的二分类问题,通过CNN对数据集进行模型训练,得出相关指标。

数据集如下:


基于CNN的意图识别 基于cnn的图像分类_2d

机场

 


基于CNN的意图识别 基于cnn的图像分类_tensorflow_02

湖泊

 

二.读取数据集

  • 数据集路径
  • 导入相关模块
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]))

基于CNN的意图识别 基于cnn的图像分类_基于CNN的意图识别_03

  • 对模型构建输入数据
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()

基于CNN的意图识别 基于cnn的图像分类_tensorflow_04

由图能看出,在训练集上的表现不是很好,还需提高模型深度,可增加卷积层及卷积核数量。模型没有表现出过拟合,如果产生过拟合可使用Dropout层或正则化参数进行调整。

五.注意事项及总结

  • 原始数据通过卷积层的shape,如输入数据为(128,128,3),filter=(3,3),filter个数为64,步长=(1,1),则输出有效区域大小为(126,126,64),其他位置用0填充。可见在池化层通过了下采样达到了压缩数据和参数数量效果。
  • 经过卷积后的数据可能参差不齐,在激活函数中可能产生梯度消失及爆炸的情况,可通过批标准化层(tf.keras.layers.BatchNormalization)将卷积后数据标准化,有利于梯度传播。
  • CNN同传统神经网络求权值方法类似,使用BP反向传播求解,在池化层,如Max Pooling,使得这个过程不可求导。在这个计算过程中,算法会记录最大值在每个小区域中的位置,在反向传播时,哪个最大值对下一层有贡献,就将残差传递到该最大值的位置。