基于CNN的图像识别
以CNN为基础完成一个CIFAR-10图像识别应用
CNN相关基础理论
卷积神经网络概述
CNN(Convolutional Neural Network,卷积神经网络)是DNN(深度神经网络)中一个非常重要的并且应用广泛的分支,CNN自从被提出,在图像处理领域得到了大量应用。
-
卷积神经网络结构 卷积神经网络按照层级可以分为5层:数据输入层、卷积层、激活层、池化层和全连接层。
-
数据输入层 数据输入层主要是对原始图像数据进行预处理
- 去均值:把输入数据各个维度都中心化为0,其目的是把样本数据的中心拉回到坐标系原点上。
- 归一化:对数据进行处理后将其限定在一定的范围内,这样可以减少各维度数据因取值范围差异而带来的干扰。
- PCA:通过提取主成分的方式避免数据特征稀疏化。
-
卷积层 卷积层通过卷积计算将样本数据进行降维采样,以获取具有空间关系特征的数据。 -
激活层 激活层对数据进行非线性变换处理,目的是对数据维度进行扭曲来获得更多连续的概率密度空间。 在CNN中,激活层一般采用的激活函数是ReLU,它具有收敛快、求梯度简单等特点。 -
池化层 池化层夹在连续的卷积层中间,用于压缩数据的维度以减少过拟合。 池化层使得CNN具有局部平移不变性,当需要处理那些只关注某个特征是否出现而不关注其出现的具体位置的任务时,局部平移不变性相当于为神经网络模型增加了一个无限强大的先验输入,这样可以极大地提高网络统计效率。 当采用最大池化策略时,可以采用最大值来代替一个区域的像素特征,这样就相当于忽略了这个区域的其他像素值,大幅度降低了数据采样维度。 -
全连接层 和常规的DNN一样,全连接层在所有的神经网络层级之间都有权重连接,最终连接到输出层。 在进行模型训练时,神经网络会自动调整层级之间的权重以达到拟合数据的目的。
卷积神经网络三大核心概念
-
稀疏交互 稀疏交互是指在深度神经网络中,处于深层次的单元可能与绝大部分输入是间接交互的。 例如: 金字塔模型,塔顶尖的点与塔底层的点就是一个间接交互的关系。如果特征信息是按照金字塔模型的走向从底层向上逐步传播的,那么可以发现,对于处在金字塔顶尖的点,它的视野可以包含所有底层输入的信息。 因为CNN具有稀疏交互性,可以通过非常小的卷积核来提取巨大维度图像数据中有意义的特征信息,因此稀疏交互性使CNN中的输出神经元之间的连接数呈指数级下降,这样神经网络计算的时间复杂度也会呈指数级下降,进而可以提高神经网络模型的训练速度。 -
参数共享 参数共享是指在一个模型的多个函数中使用相同的参数,在卷积计算中参数共享可以使神经网络模型只需要学习一个参数集合,而不需要针对每一个位置学习单独的参数集合。 参数共享可以显著降低需要存储的参数数量,进而提高神经网络的统计效率。 -
等变表示 等变表示是指当一个函数输入改变时,其输出也以同样的方式改变 这个特性的存在说明:卷积函数具备等变性,经过卷积计算之后可以等变地获取数据的特征信息。
TensorFlow 2.0 API
tf.keras.Sequential
Sequential是一个方法类,可以帮助我们轻而易举地以堆叠神经网络层的方式集成构建一个复杂的神经网络模型。
Sequential提供了丰富的方法,利用这些方法可以快速地实现神经网络模型的网络层级集成、神经网络模型编译、神经网络模型训练和保存,以及神经网络模型加载和预测。
-
神经网络模型的网络层级集成 使用Sequential().add()方法来实现神经网络层级的集成,可以根据实际需要将tf.keras.layers中的各类神经网络层级添加进去 import tensorflow as tf
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(256, activation="relu"))
model.add(tf.keras.layers.Dense(128, activation="relu"))
model.add(tf.keras.layers.Dense(2, activation="softmax"))
完成了三个全连接神经网络层级的集成,构建了一个全连接神经网络模型。
-
神经网络模型编译 在完成神经网络层级的集成之后需要对神经网络模型进行编译,只有编译后才能对神经网络模型进行训练。 对神经网络模型进行编译是将高阶API转换成可以直接运行的低阶API Sequential().compile()提供了神经网络模型的编译功能 model.compile(loss="sparse_categorical_crossentropy", optimizer=
tf.keras.optimizers.Adam(0.01), metrics=["accuracy"] )
在compile方法中需要定义三个参数,分别是loss、optimizer和metrics。
- loss参数用来配置模型的损失函数,可以通过名称调用tf.losses API中已经定义好的loss函数
- optimizer参数用来配置模型的优化器,可以调用tf.keras.optimizers API 配置模型所需要的优化器
- metrics参数用来配置模型评价的方法,如accuracy、mse等。
-
神经网络模型训练和保存 在神经网络模型编译后,可以使用准备好的训练数据对模型进行训练 Sequential().fit()方法提供了神经网络模型的训练功能。 Sequential().fit()有很多集成的参数需要配置,其中主要的配置参数如下:
-
x:配置训练的输入数据,可以是array或者tensor类型。 -
y:配置训练的标注数据,可以是array或者tensor类型。 -
batch_size:配置批大小,默认值是32。 -
epochs:配置训练的epochs的数量。 -
verbose:配置训练过程信息输出的级别,共有三个级别,分别是0、1、2。
- 0代表不输出任何训练过程信息
- 1代表以进度条的方式输出训练过程信息
- 2代表每个epoch输出一条训练过程信息。
-
validation_split:配置验证数据集占训练数据集的比例,取值范围为0~1 -
validation_data:配置验证数据集。如果已经配置validation_split参数,则可以不配置该参数。 如果同时配置validation_split和validation_data参数,那么validation_split参数的配置将会失效。 -
shuffle:配置是否随机打乱训练数据。当配置steps_per_epoch为None时,本参数的配置失效。 -
initial_epoch:配置进行fine-tune时,新的训练周期是从指定的epoch开始继续训练的。 -
steps_per_epoch:配置每个epoch训练的步数 可以使用save()或者save_weights()方法保存并导出训练得到的模型 在使用这两个方法时需要分别配置以下参数: -
神经网络模型加载和预测 当需要使用模型进行预测时,可以使用tf.keras.models中的load_model()方法重新加载已经保存的模型文件。 在完成模型文件的重新加载之后,可以使用predict()方法对数据进行预测输出。 在使用这两个方法时需要分别进行如下参数配置:
tf.keras.layers.Conv2D
使用Conv2D可以创建一个卷积核来对输入数据进行卷积计算,然后输出结果,其创建的卷积核可以处理二维数据。
Conv1D可以用于处理一维数据,Conv3D可以用于处理三维数据。
在进行神经网络层级集成时,如果使用该层作为第一层级,则需要配置input_shape参数。
在使用Conv2D时,需要配置的主要参数如下:
-
filters:配置输出数据的维度,数值类型是整型。 -
kernel_size:配置卷积核的大小。这里使用的是二维卷积核,因此需要配置卷积核的长和宽。 数值是包含两个整型元素值的列表或者元组。 -
strides:配置卷积核在做卷积计算时移动步幅的大小,分为X、Y两个方向的步幅。 数值是包含两个整型元素值的列表或者元组,当 X、Y 两个方向的步幅大小一样时,只需要配置一个步幅即可。 -
padding:配置图像边界数据处理策略。SAME表示补零,VALID表示不进行补零。 -
在进行卷积计算或者池化时都会遇到图像边界数据处理的问题,当边界像素不能正好被卷积或者池化的步幅整除时,只能在边界外补零凑成一个步幅长度,或者直接舍弃边界的像素特征。 -
data_format:配置输入图像数据的格式,默认格式是channels_last,也可以根据需要设置成channels_first。 图像数据的格式分为**channels_last(batch, height, width, channels)和channels_first(batch, channels, height, width)**两种。 -
dilation_rate:配置使用扩张卷积时每次的扩张率。 -
activation:配置激活函数,如果不配置则不会使用任何激活函数。 -
use_bias:配置该层的神经网络是否使用偏置向量 -
kernel_initializer:配置卷积核的初始化 -
bias_initializer:配置偏置向量的初始化
tf.keras.layers.MaxPool2D
MaxPool2D的作用是对卷积层输出的空间数据进行池化,采用的池化策略是最大值池化。
在使用MaxPool2D时需要配置的参数如下:
-
pool_size:配置池化窗口的维度,包括长和宽。数值是包含两个整型元素值的列表或者元组。 -
strides:配置卷积核在做池化时移动步幅的大小,分为 X、Y 两个方向的步幅。数值是包含两个整型元素值的列表或者元组,默认与pool_size相同。 -
padding:配置处理图像数据进行池化时在边界补零的策略。SAME表示补零,VALID表示不进行补零。 在进行卷积计算或者池化时都会遇到图像边界数据的问题,当边界像素不能正好被卷积或者池化的步幅整除时,就只能在边界外补零凑成一个步幅长度,或者直接舍弃边界的像素特征。 -
data_format:配置输入图像数据的格式,默认格式是channels_last,也可以根据需要设置成channels_first。 在进行图像数据处理时,图像数据的格式分为channels_last(batch, height, width, channels)和channels_first(batch, channels, height, width)两种。
tf.keras.layers.Flatten与tf.keras.layer.Dense
-
Flatten将输入该层级的数据压平,不管输入数据的维度数是多少,都会被压平成一维。 这个层级的参数配置很简单,只需要配置data_format即可。 data_format可被设置成channels_last或channels_first,默认值是channels_last。 -
Dense提供了全连接的标准神经网络。
tf.keras.layers.Dropout
对于Dropout在神经网络模型中具体作用的认识,业界分为两派
- 一派认为Dropout极大简化了训练时神经网络的复杂度,加快了神经网络的训练速度
- 另一派认为Dropout的主要作用是防止神经网络的过拟合,提高了神经网络的泛化性。
简单来说,Dropout的工作机制就是每步训练时按照一定的概率随机使神经网络的神经元失效,这样可以极大降低连接的复杂度。
同时由于每次训练都是由不同的神经元协同工作的,这样的机制也可以很好地避免数据带来的过拟合,提高了神经网络的泛化性。
在使用Dropout时,需要配置的参数如下:
- rate:配置神经元失效的概率。
- noise_shape:配置Dropout的神经元。
- seed:生成随机数
tf.keras.optimizers.Adam
Adam是一种可以替代传统随机梯度下降算法的梯度优化算法,它是由OpenAI的Diederik Kingma和多伦多大学的Jimmy Ba在2015年发表的ICLR论文(Adam:A Method for Stochastic Optimization)中提出的。
Adam具有计算效率高、内存占用少等优势,自提出以来得到了广泛应用。
Adam和传统的梯度下降优化算法不同,它可以基于训练数据的迭代情况来更新神经网络的权重,并通过计算梯度的一阶矩估计和二阶矩估计来为不同的参数设置独立的自适应学习率。
Adam适用于解决神经网络训练中的高噪声和稀疏梯度问题,它的超参数简单、直观并且只需要少量的调参就可以达到理想的效果。
官方推荐的最优参数组合为(alpha=0.001, beta_1=0.9, beta_2=0.999, epsilon=10E-8),在使用时可以配置如下参数。
- learning_rate:配置学习率,默认值是0.001。
- beta_1:配置一阶矩估计的指数衰减率,默认值是0.9。
- beta_2:配置二阶矩估计的指数衰减率,默认值是0.999。
- epsilon:该参数是一个非常小的数值,防止出现除以零的情况。
- amsgrad:配置是否使用AMSGrad。
- name:配置优化器的名称。
项目工程结构设计
整个项目工程结构分为两部分:
把所有的Python代码文件放在根目录下
其他静态文件、训练数据文件和模型文件等都放在文件夹中。
项目分为四个部分:配置工具、CNN模型、执行器和应用程序。
- 配置工具提供了将神经网络超参数配置动作通过配置文件进行调整的功能
- CNN模型是为了完成本项目的需求而设计的卷积神经网络
- 执行器中定义了训练数据读取、训练模型保存、模型预测等一系列方法
- 应用程序是一个基于Flask的简单Web应用程序,用于人机交互。
文件夹中:
- model_dir 存放的是训练结果模型文件,也是在预测时加载模型文件的路径
- predict_img 存放的是上传的图像,通过调用预测程序进行预测
- train_data 存放的是训练数据
- test_data : 存放测试数据
- static和templates存放的是Web应用程序所需的HTML、JS等静态文件
项目实现代码
工具类实现
定义一个工具类来读取配置文件中的配置参数,当需要调参时,只需对配置文件中的参数进行调整即可
神经网络超参数的配置文件:
[strings]
mode = train
working_directory = model_dir
dataset_path=train_data/
[ints]
num_dataset_classes=10
dataset_size=50000
im_dim=32
num_channels = 3
num_files=5
images_per_file=10000
batch_size=32
[floats]
rate=0.5
import configparser
def get_config(config_file='config.ini'):
parser=configparser.ConfigParser()
parser.read(config_file)
_conf_ints = [(key, int(value)) for key, value in parser.items ('ints')]
_conf_floats = [(key, float(value)) for key, value in parser.items ('floats')]
_conf_strings = [(key, str(value)) for key, value in parser.items ('strings')]
return dict(_conf_ints + _conf_floats + _conf_strings)
cnnModel实现
采用tf.keras这个高阶API类,定义了3层卷积神经网络,输出维度分别是32、64、128
最后在输出层定义一层全连接神经网络,输出维度是10
在定义卷积神经网络过程中,按照一个卷积神经网络标准的结构进行定义,使用最大池化(maxpooling)策略进行降维特征提取,使用Dropout防止过拟合。
引入所需要的依赖包: tensorflow、numpy以及自定义配置获取包getConfig
import tensorflow as tf
import numpy as np
import getConfig
gConfig={}
gConfig=getConfig.get_config(config_file='config.ini')
class cnnModel(object):
def __init__(self , rate):
self.rate=rate
def createModel(self):
model=tf.keras.Sequential()
'''
添加一个二维卷积层,输出数据维度为32,卷积核维度为3×3。
输入数据维度为[32,32,3],这里的维度是WHC格式的,意思是输入图像像素为32×32的尺寸,使用3通道也就是RGB的像素值。
同样,如果图像是64×64尺寸的,则可以设置输入数据维度为[64,64,3]
'''
model.add(tf.keras.layers.Conv2D(32, (3,3), kernel_initializer='he_normal', strides=1, padding='same', activation='relu',input_shape=[32,32,3], name="conv1"))
model.add(tf.keras.layers.MaxPool2D((2,2), strides=1,padding='same', name="pool1"))
model.add(tf.keras.layers.BacthNormalization())
model.add(tf.keras.layers.Conv2D(64, (3,3), kernel_initializer='he_normal', strides=1, padding='same',activation='relu', name="conv2"))
model.add(tf.keras.layers.MaxPool2D((2,2), strides=1,padding='same', name="pool2"))
model.add(tf.keras.layers.BacthNormalization())
model.add(tf.keras.layers.Conv2D(128, (3,3), kernel_initializer='he_normal', strides=1, padding='same',activation='relu', name="conv3"))
model.add(tf.keras.layers.MaxPool2D((2,2), strides=1,padding='same', name="pool3"))
model.add(tf.keras.layers.BacthNormalization())
'''
在经过卷积和池化完成特征提取之后,紧接着就是一个全连接的深度神经网络。在将数据输入深度神经网络之前主要进行数据的Flatten操作,就是将之前
长、宽像素值三个维度的数据压平成一个维度,这样可以减少参数的数量。因此在卷积层和全连接神经网络之间添加一个Flatten层
'''
model.add(tf.keras.layers.Flatten(name="flatten"))
model.add(tf.keras.layers.Dropout(rate=self.rate, name="d3"))
model.add(tf.keras.layers.Dense(10, activation='softmax'))
model.compile(loss="categorical_crossentropy", optimizer=tf.keras.optimizers.Adam(), metrics=["accuracy"])
return model
执行器实现
执行器的主要作用是读取训练数据、实例化神经网络模型、循环训练神经网络模型、保存神经网络模型和调用模型完成预测。
在执行器的实现上需要定义以下函数:
- read_data函数用于读取训练集数据
- create_model函数用于进行神经网络的实例化
- train函数用于进行神经网络模型的循环训练和保存
- predict函数用于进行模型加载和结果预测
import tensorflow as tf
import numpy as np
from cnnModel import cnnModel
import os
import pickle
import getConfig
label_names_dict=classes = ('plane', 'car', 'bird', 'cat', 'deer','dog', 'frog', 'horse', 'ship', 'truck')
gConfig = {}
gConfig=getConfig.get_config(config_file="config.ini")
def read_data(dataset_path, im_dim, num_channels, num_files,images_per_file):
files_names = os.listdir(dataset_path)
"""
在CIFAR-10中已经为我们标注和准备好了数据。
在训练集中一共有50000个训练样本,放到5个二进制文件中,每个样本有 3072个像素点,维度是32×32×3
"""
dataset_array = np.zeros(shape=(num_files * images_per_file, im_dim, im_dim, num_channels))
dataset_labels = np.zeros(shape=(num_files * images_per_file), dtype=np.uint8)
index = 0
for file_name in files_names:
if file_name[0:len(file_name)-1] == "data_batch_":
print("正在处理数据 : ", file_name)
data_dict = unpickle_patch(dataset_path + file_name)
images_data = data_dict[b"data"]
images_data_reshaped = np.reshape(images_data,newshape= (len(images_data), im_dim, im_dim, num_channels))
dataset_array[index * images_per_file:(index + 1) * images_per_file, :, :, :] = images_data_reshaped
dataset_labels[index * images_per_file: (index + 1) * images_per_file] = data_dict[b"labels"]
index = index + 1
return dataset_array, dataset_labels
def unpickle_patch(file):
patch_bin_file = open(file, 'rb')
patch_dict = pickle.load(patch_bin_file, encoding='bytes')
patch_bin_file.close()
return patch_dict
def create_model():
if 'pretrained_model'in gConfig:
model=tf.keras.models.load_model(gConfig['pretrained_model'])
return model
ckpt=tf.io.gfile.listdir(gConfig['working_directory'])
if ckpt:
model_file=os.path.join(gConfig['working_directory'],ckpt[-1])
print("Reading model parameters from %s" % model_file)
model=tf.keras.models.load_model(model_file)
return model
else:
model=cnnModel(gConfig['rate'])
model=model.createModel()
return model
def train():
dataset_array, dataset_labels = read_data(dataset_path=gConfig['dataset_path'], im_dim=gConfig['im_dim'], num_channels=gConfig['num_channels'], num_files=gConfig ['num_files'], images_per_file=gConfig['images_per_file'])
dataset_array= dataset_array.astype('float32')/255
dataset_labels=tf.keras.utils.to_categorical(dataset_labels,10)
model=create_model()
model.fit(dataset_array, dataset_labels, verbose=1, epochs=100, validation_split=0.2)
filename=gConfig["modelfile"]
print(filename)
checkpoint_path = os.path.join(gConfig['working_directory'], filename)
model.save(checkpoint_path)
if gConfig['mode']=='server':
ckpt=os.listdir(gConfig['working_directory'])[0]
checkpoint_path = os.path.join(gConfig['working_directory'], ckpt )
model=tf.keras.models.load_model(checkpoint_path)
def predict(data):
predicton=model.predict(data)
index=tf.math.argmax(predicton[0]).numpy()
return label_names_dict[index]
if __name__=='__main__':
if gConfig['mode']=='train':
train()
elif gConfig['mode']=='server':
print('请运行app.py')
对训练数据进行归一化处理
Web应用实现
Web应用的主要功能包括完成页面交互、图片格式判断、图片上传以及预测结果的返回展示。
使用Flask这个轻量级Web应用框架来实现简单的页面交互和预测结果展示功能。
import flask
import werkzeug
import os
import execute
import getConfig
import flask
import numpy as np
from PIL import Image
gConfig = {}
gConfig = getConfig.get_config(config_file='config.ini')
secure_filename = ""
app = flask.Flask("imgClassifierWeb")
def CNN_predict():
global secure_filename
img = Image.open(os.path.join(app.root_path, secure_filename))
r, g, b=img.split()
r_arr=np.array(r)
g_arr=np.array(g)
b_arr=np.array(b)
img=np.concatenate((r_arr, g_arr, b_arr))
image=img.reshape([1,32,32,3])/255
predicted_class=execute.predict(image)
return flask.render_template(template_name_or_list="prediction_result.html", predicted_class=predicted_class)
app.add_url_rule(rule="/predict/", endpoint="predict", view_func=CNN_predict)
def upload_image():
global secure_filename
if flask.request.method == "POST":
img_file = flask.request.files["image_file"]
secure_filename = werkzeug.utils.secure_filename(img_file.filename)
print(secure_filename)
img_path = os.path.join(app.root_path, secure_filename)
img_file.save(img_path)
print("图片上传成功.")
return flask.redirect(flask.url_for(endpoint="predict"))
return "图片上传失败"
app.add_url_rule(rule="/upload/", endpoint="upload", view_func=upload_image, methods=["POST"])
def redirect_upload():
return flask.render_template(template_name_or_list="upload_image.html")
app.add_url_rule(rule="/", endpoint="homepage", view_func=redirect_upload)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7777, debug=False)
程序下载
https://download.csdn.net/download/first_bug/85447520
参考
《走向TensorFlow2.0》
|