目录

CNN

项目中可能用到核心API

tf.keras.Sequential

tf.keras.layers.Conv2D

tf.keras.layers.MaxPool2D

tf.keras.layers.Flatten

tf.keras.layers.Dense

tf.keras.layers.Droupt

tf.keras.optimizers.Adam

代码工程

项目环境

主要代码模块

导入包

程序入口

获取模型/创建模型

设计CNN模型

训练模型

模型预测


这是我最近在看的书中的一个案例,是通过CNN来做图像分类。案例本来是封装成一个python web工程,通过浏览器上传一个图片来识别得出结果。我自己计划把它改造一下,放到jupyter notebook里去,方便后续自己做调试以及优化。

CNN

CNN(Convolutional Neural Network,卷积神经网络)是DNN(深度神经网络)中一个分支。卷积神经网络一般分为5层:

  • 数据输入层。主要对源数据做基本的数据处理,如去均值、归一化、PCA。

  • 卷积层。通过卷积计算将样本数据进行降维采样,以获取具有空间关系特征的数据。

  • 激活层。激活层对数据进行非线性变换处理,目的是对数据维度进行扭曲来获得更多连续的概率密度空间。在CNN中,激活层一般才用的激活函数是ReLU,它具有收敛快、求梯度简单等特点。

  • 池化层。池化层加载连续的卷积层中间,用于压缩数据的维度以减少过拟合。

  • 全连接层。全连接层在所有的神经网络层级之间都有权重连接,最终连接到输出层。

 

卷积神经网络三大核心概念

  • 稀疏交互:指在深度神经网络中,处于深层次的单元可能与绝大部分输入是间接交互的。

  • 参数共享:指在一个模型的多个函数中使用的参数。

  • 等变表示:指当一个函数输入改变时,如果其输出也以同样的方式改变,那么这个函数就具备等变表示性。

项目中可能用到核心API

tf.keras.Sequential

以堆叠神经网络的方式构建一个复杂的神经网络模型,快速地实现神经网络模型的网络层级集成、神经网络模型编译、神经网络模型训练和保存,以及神经网络模型加载和预测。主要用到了以下API:

add():神经网络层级的集成。

compile():神经网络模型编译。

fit():神经网络模型训练。

save():神经网络模型保存。

load_model():模型加载。

tf.keras.layers.Conv2D

创建一个卷积核来对输入数据进行卷积计算,然后输出结果,可以处理二维数据。如果用于第一层,需要配置input_shape参数。

tf.keras.layers.MaxPool2D

MaxPool2D的作用是对卷积层输出的空间数据进行池化,采用的池化策略是最大值池化。

tf.keras.layers.Flatten

将输入该级的数据压平,不管输入数据的维度数是多少,都会被压平成一维。

tf.keras.layers.Dense

提供了全连接的标准神经网络。

tf.keras.layers.Droupt

Droupt的工作机制就是每步训练时按照一定的概率随机使神经网络的神经元失效,这样可以极大降低连接的复杂度。同时由于每次训练都是由不同的神经元协同工作的,这样的机制也可以很好地避免数据带来的过拟合,提高了神经网络的泛化性。

tf.keras.optimizers.Adam

Adam是一种可以代替传统随机梯度下降算法的梯度优化算法。

代码工程

书本的代码原本是web形式的,基于Flask的简单Web应用程序,方便交互。但我自己平时用jupyter notebook比较多,不管在公司还是在家,都比较方便敲代码。因此,我把书本的源码抽取到Jupyter notebook那里去了。

项目环境

我自己是用anaconda来搭建python环境,并独立建一个新的python环境,版本为python3.7。以下是我环境中的所有库版本信息,大家可以采用“requirements.txt”方式来导入环境。我把里面的内容贴一下:

 

absl-py==0.9.0
asgiref==3.2.10
astor==0.8.1
backcall==0.2.0
cachetools==4.1.1
certifi==2020.6.20
chardet==3.0.4
decorator==4.4.2
Django==3.1
docopt==0.6.2
gast==0.2.2
google-auth==1.20.0
google-auth-oauthlib==0.4.1
google-pasta==0.2.0
grpcio==1.30.0
h5py==2.10.0
idna==2.10
image==1.5.32
importlib-metadata==1.7.0
ipykernel @ file:///tmp/build/80754af9/ipykernel_1596206598566/work/dist/ipykernel-5.3.4-py3-none-any.whl
ipython @ file:///tmp/build/80754af9/ipython_1593447368578/work
ipython-genutils==0.2.0
jedi @ file:///tmp/build/80754af9/jedi_1596490743326/work
jupyter-client @ file:///tmp/build/80754af9/jupyter_client_1594826976318/work
jupyter-core==4.6.3
Keras-Applications==1.0.8
Keras-Preprocessing==1.1.2
Markdown==3.2.2
numpy==1.19.1
oauthlib==3.1.0
opt-einsum==3.3.0
parso==0.7.0
pexpect @ file:///tmp/build/80754af9/pexpect_1594383317248/work
pickleshare @ file:///tmp/build/80754af9/pickleshare_1594384075987/work
Pillow==7.2.0
pipreqs==0.4.10
prompt-toolkit==3.0.5
protobuf==3.12.4
ptyprocess==0.6.0
pyasn1==0.4.8
pyasn1-modules==0.2.8
Pygments==2.6.1
python-dateutil==2.8.1
pytz==2020.1
pyzmq==19.0.1
requests==2.24.0
requests-oauthlib==1.3.0
rsa==4.6
six==1.15.0
sqlparse==0.3.1
tensorboard==2.0.2
tensorflow==2.0.0
tensorflow-estimator==2.0.1
termcolor==1.1.0
tornado==6.0.4
traitlets==4.3.3
urllib3==1.25.10
wcwidth @ file:///tmp/build/80754af9/wcwidth_1593447189090/work
Werkzeug==1.0.1
wrapt==1.12.1
yarg==0.1.9
zipp==3.1.0

 

当然,大家也可以只安装第三方库,可参考:

 

numpy==1.19.1
tensorflow==2.0.0
Pillow==7.2.0

 

如何根据“requirements.txt”文件导入依赖库,可以参考我另外一篇文章(https://blog.csdn.net/ispeasant/article/details/107937439)。

其中,我已把“requirements.txt”文件放到“imageClass\package”文件夹中,说明:

requirements_all.txt:环境中所有库。

requirements.txt:只有项目中用到的第三方库。

 

主要代码模块

导入包

 

import tensorflow as tf
import numpy as np
import pickle
import os
import sys
from pathlib import Path
from PIL import Image

 

程序入口

 

if __name__ == "__main__":
    #获取模型
    model_file_name = "cnn_model_20200811_03.h5"
    model = getModel(model_file_name)
    
    # 预测图片
    img = Image.open('predict_img/deer.png')
    
    #将图像文件的格式转换为RGB
    img = img.convert("RGB")
    
    #分别获取r,g,b三元组的像素数据并进行拼接
    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))
    
    #将拼接得到的数据按照模型输入维度需要转换为(32,32,3),并对数据进行归一化
    image = img.reshape([1, 32, 32, 3])/255

    # 获取图片分类名称,保存到一个字典里
    file = "train_data/" + "batches.meta"
    patch_bin_file = open(file, 'rb')
    label_names_dict = pickle.load(patch_bin_file)["label_names"]

    # 预测,并打印结果
    predicton=model.predict(image)
    index=tf.math.argmax(predicton[0]).numpy()

    print(label_names_dict[index])

 

获取模型/创建模型

 

#创建模型
def getModel(file_name):
    #判断模型文件是否存在
    model_file = Path("model_dir/" + file_name)
    
    #有训练好的模型,就直接使用
    if model_file.is_file():
        model=tf.keras.models.load_model(model_file)
        return model
    
    #否则,重新训练一个
    else:
        model = createCNNModel()
        model = trainModel(model,file_name)
        return model

 

设计CNN模型

 

#建CNN模型
def createCNNModel():
    # Droupt神经元失效概率
    rate = 0.5
    
    # 使用Sequential来构建网络模型
    model = tf.keras.Sequential() 
    
    # 添加一个二维卷积层,输出数据维度为64,卷积核维度为3X3,输入数据为[32,32,3]
    model.add(tf.keras.layers.Conv2D(64, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                            input_shape=(32,32,3),name="conv1"))
    
    # 添加第二个二维卷积层
    model.add(tf.keras.layers.Conv2D(64, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                            name="conv2"))
    
    # 添加一个二维池化层,使用最大池化,池化维度为2X2
    model.add(tf.keras.layers.MaxPool2D((2, 2), strides=2, padding='valid', name="pool1"))
    
    # 添加一个Dropout层
    model.add(tf.keras.layers.Dropout(rate, name="d1"))
    
    # 添加一个批量池化层
    model.add(tf.keras.layers.BatchNormalization())
    
    # 添加一个卷积层
    model.add(tf.keras.layers.Conv2D(128, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                           name="conv3"))
    
    # 添加一个卷积层
    model.add(tf.keras.layers.Conv2D(128, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                            name="conv4"))
    
    # 添加一个二维池化层,使用最大池化
    model.add(tf.keras.layers.MaxPool2D((2, 2), strides=2, padding='valid', name="pool2"))
    
    # 添加一个Dropout层
    model.add(tf.keras.layers.Dropout(rate, name="d2"))
    
    # 添加一个批量池化层
    model.add(tf.keras.layers.BatchNormalization())
    
    # 添加一个卷积层
    model.add(tf.keras.layers.Conv2D(256, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                           name="conv5"))
    
    # 添加一个卷积层
    model.add(tf.keras.layers.Conv2D(256, 3, kernel_initializer='he_normal', strides=1, activation='relu', padding='same',
                                           name="conv6"))
    
    # 添加一个二维池化层,使用最大池化
    model.add(tf.keras.layers.MaxPool2D((2, 2), strides=2, padding='valid', name="pool3"))
    
    # 添加一个Dropout层
    model.add(tf.keras.layers.Dropout(rate, name="d3"))
    
    # 添加一个批量池化层
    model.add(tf.keras.layers.BatchNormalization())
    
    # 输入深度神经网络之前进行数据的Flatten操作,将之前长、宽像素值三个维度的数据压平成一个维度,可以减少参数的数量
    model.add(tf.keras.layers.Flatten(name="flatten"))
    
    # 添加一个Droupt层
    model.add(tf.keras.layers.Dropout(rate))
    
    # 全连接,采用relu激活函数
    model.add(tf.keras.layers.Dense(128, activation='relu',kernel_initializer='he_normal'))
    
    # 添加一个Droupt层
    model.add(tf.keras.layers.Dropout(rate))
    
    # 全连接,采用softmax激活函数
    model.add(tf.keras.layers.Dense(10, activation='softmax',kernel_initializer='he_normal'))
    
    # 神经网络模型设计完后,需要对模型进行编译,生成可训练的模型。
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
        
    print(model.summary())
        
    return model

 

训练模型

 

#训练模型
def trainModel(model,model_file_name):
    #处理dataset、testset  --开始--  
    
    # 训练集路径
    dataset_path = "train_data/"
    # 图像输入的尺寸
    im_dim = 32
    num_channels = 3
    # 训练文件数
    num_files = 6
    # 每个训练文件的图像数量
    images_per_file = 10000
    # 测试集路径
    test_path = "test_data/"
    #  测试文件数
    num_files_test = 1

    # 获取文件
    files_names = os.listdir(dataset_path)
    # 用数组存放图像二进制数据
    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)
    
    # 将数据转换成指定维度数据,由im_dim = 32,num_channels = 3。即将数据转成[32,32,3]
    index = 0
    for file_name in files_names:
        if file_name[0:len(file_name)-1] == "data_batch_":
            print("正在处理数据 : ", file_name)

            patch_bin_file = open(dataset_path + file_name, 'rb')
            data_dict = pickle.load(patch_bin_file, encoding='bytes')
            images_data = data_dict[b"data"]
            print(images_data.shape)

            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

    # 到测试集,和训练集一样的套路,当然可以把上面的代码封装成共用的方法
    files_names = os.listdir(test_path)
    test_array = np.zeros(shape=(num_files_test * images_per_file, im_dim, im_dim, num_channels))
    test_labels = np.zeros(shape=(num_files_test * 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)

            patch_bin_file = open(test_path + file_name, 'rb')
            data_dict = pickle.load(patch_bin_file, encoding='bytes')
            images_data = data_dict[b"data"]
            print(images_data.shape)

            images_data_reshaped = np.reshape(images_data, newshape=(len(images_data), im_dim, im_dim, num_channels))

            test_array[index * images_per_file:(index + 1) * images_per_file, :, :, :] = images_data_reshaped

            test_labels[index * images_per_file:(index + 1) * images_per_file] = data_dict[b"labels"]
            index = index + 1

    # 数据进行归一化处理,取值范围为(0,1)
    dataset_array=dataset_array.astype('float32')/255
    test_array=test_array.astype('float32')/255
    
    # 对标注数据进行one-hot编码
    dataset_labels=tf.keras.utils.to_categorical(dataset_labels,10)
    test_labels=tf.keras.utils.to_categorical(test_labels,10)
    #处理dataset、testset  --结束--         

    # 模型训练
    model.fit(dataset_array,dataset_labels,verbose=1,epochs=2,validation_data=(test_array,test_labels))

    # 保存模型
    checkpoint_path = os.path.join("model_dir/", model_file_name)
    model.save(checkpoint_path)
    sys.stdout.flush()
    
    return model

 

模型预测

在项目目录的“predict_img”文件夹存放着用于预测的图片,只需在程序入口代码那部分进行配置就可以。

目前有以下几个图片可用于预测:

如果你想训练一个模型,可以直接修改模型文件名字,如果模型文件目录(model_dir)里没有这个文件,程序会自动重新训练一个新的模型,模型文件名就是你刚刚配置的名字。

如果大家需要源码,可关注本人公众号,回复“2020081201”获取下载链接。

 


 

只要自己有时间,都尽量写写文章,与大家交流分享。

本人公众号:

CSDN博客地址:https://blog.csdn.net/ispeasant

 

 

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐