手搓Lenet的keras实现

本文基于的开发环境介绍:

手搓Lenet的keras实现_第1张图片

经典的lenet网络结构:

手搓Lenet的keras实现_第2张图片

代码:

import keras
from keras.models import Sequential
from keras.layers import Input,Dense,Activation,Conv2D,MaxPooling2D,Flatten
from keras.datasets import mnist

(x_train,y_train),(x_test,y_test) = mnist.load_data()
x_train = x_train.reshape(-1, 28, 28, 1)    #######
x_train = x_train.astype("float32")
print(x_train.shape)
y_train = y_train.astype("float32")
x_test = x_test.reshape(-1,28,28,1)
x_test = x_test.astype("float32")
y_test = y_test.astype("float32")

print(y_train)
x_train /= 255
x_test /= 255

from keras.utils import np_utils
y_train_new = np_utils.to_categorical(num_classes=10,y=y_train)
print(y_train_new)
y_test_new = np_utils.to_categorical(num_classes=10,y=y_test)

def LeNet_5():
    model = Sequential()
    model.add(Conv2D(filters=6,kernel_size=(5,5),padding="valid",activation="tanh",input_shape=[28, 28, 1]))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Conv2D(filters=16,kernel_size=(5,5),padding="valid",activation="tanh"))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Flatten())
    model.add(Dense(120,activation="tanh"))
    model.add(Dense(84,activation="tanh"))
    model.add(Dense(10,activation="softmax"))
    return model

def train_model():
    model = LeNet_5()
    model.compile(optimizer="adam",loss="categorical_crossentropy",metrics=["accuracy"])
    model.fit(x_train,y_train_new,batch_size=32,epochs=1,verbose=1,validation_split=0.2,shuffle=True)
    return model

model = train_model()
model.summary()

loss,accuracy = model.evaluate(x_test,y_test_new)
print(loss,accuracy)

这里的padding表示补0策略,有“valid”和“same” 。“valid”代表只进行有效的卷积,即对边界数据不处理。“same”代表保留边界处的卷积结果,通常会导致输出shape与输入shape相同。默认是valid,但是考虑到几层卷积下来,可能卷积连接有问题,一般会用same,这里用的valid.

运行结果:

手搓Lenet的keras实现_第3张图片

最终输出,可以看到lenet 每层的参数情况:

手搓Lenet的keras实现_第4张图片

调整EPOCHES再次训练,得到精度更高的模型:

手搓Lenet的keras实现_第5张图片

epochs代表要把整个训练样本迭代6次,batch size参数表示一次训练中取32条数据进行训练。

32*1500=48000,表示使用到的训练数据集大小。

训练完成后,我们验证模型的推理能力,代码如下:

import numpy as np
import tensorflow as tf
demo = tf.reshape(x_test[9],(-1,28,28,1))
print(np.argmax(model.predict(demo)))

参数推算:

当输入含有多个通道时,对应的卷积核也应该有相同的通道数。假设输入图片的通道数为CinCin,输入数据的形状是C_{in}\times{H_{in}}\times{W_{in}} 

  1. 对每个通道分别设计一个2维数组作为卷积核,卷积核数组的形状是C_{in}\times{k_h}\times{k_w}

  2. 对任一通道C_{in} \in [0, C_{in}),分别用大小为k_h\times{k_w}的卷积核在大小为H_{in}\times{W_{in}}的二维数组上做卷积。

  3. 将这C_{in}个通道的计算结果相加,得到的是一个形状为H_{out}\times{W_{out}}的二维数组。

根据网络结构计算各层参数:

第一层: 5*5卷积核6个,所以权重数为5*5*6=150个,一个核对应一个通道, 每个通道一个偏置,所以共有6个bias参数,所以,第一个卷积层的参数个数为150+6=156个,pegasus统计没有包括bias,只包括卷积核,所以为150个。計算公式為:

手搓Lenet的keras实现_第6张图片

第二层,池化层没有参数,所以参数个数为0个。

第三层,卷积层,卷积核为16个5*5,但是由于上一层的卷积核个数等于上层输出的feature map通道数,可以得知第二层输入的feature map为6通道,所以实际上的卷积核shape为 16个5*5*6大小,也就是16*5*5*6=2400个参数。再加上16个bias,总共2416个参数。

第四层是池化层,没有参数.

第五层是降维层,二维降1维,没有参数。

第六层是全连接层FC,参数个数为256*120+ 120= 30840

第七层是全连接层FC:参数个数为120*84+84=10164

第八层是全连接层FC: 参数个数是840*10+10=850

DUMP各层信息:

print(model.layers[0].name)
print(model.layers[1].name)
print(model.layers[2].name)
print(model.layers[3].name)
print(model.layers[4].name)
print(model.layers[5].name)
print(model.layers[6].name)
print(model.layers[7].name)

手搓Lenet的keras实现_第7张图片

可以看到各层的名称成功获取到了。

之后获取层的参数,以第一层为例:

weights, biases = model.layers[0].get_weights()
print(weights)
print(biases)

手搓Lenet的keras实现_第8张图片

然后是BIAS,果然有6个,然后卷积核是训练出来的,并非是设计出来的。

手搓Lenet的keras实现_第9张图片

网络的拓扑结构为:

保存模型:

jupter中新添加代码块:

 编辑代码:

model.save("lenet.h5")
model.save_weights("lenet_weight.h5")

运行后,生成两个文件 lenet.h5和lenet_weight.h5

save()保存的模型结果,它既保持了模型的图结构,又保存了模型的参数,而save_weights它只保存了模型的参数,但并没有保存模型的图结构。所以它的size也要比lenet.h5小很多。

netron查看lenet.h5:

netron原生支持.h5格式的网络模型文件,用netron打开lenet.h5,可以看到网络结构如下:

手搓Lenet的keras实现_第10张图片

还可以保存未训练前的网络,比如在下面加入的保存lenet_raw.h5的语句:

手搓Lenet的keras实现_第11张图片

 lenet_raw.h5它保存了模型的图结构,但应该没有保存模型的初始化参数,所以也要比lenet.h5小一些。

查看模型内容:

模型为 Hierarchical Data Format (version 5) data个格式,可以通过工具查看内容。

ubuntu下安装hdfview可以查看保存模型的内容,ubuntu安装方式直接 sudo apt install hdfview即可. 经测试,UBUNTU版本的hdfview打不开对应的文件,不得已切换为windows10系统,下载下图对应版本的工具

手搓Lenet的keras实现_第12张图片

可以看到,工具下,模型结构一目了然。

转ONNX

首先执行sudo pip3 install keras2onnx, sudo pip3 install onnxmltools安装转换包

手搓Lenet的keras实现_第13张图片

手搓Lenet的keras实现_第14张图片

另一种方式:

上面使用CNN网络实现了mnist数据集的分类网络,其实除了CNN,RNN循环神经网络也可以实现同样算法,LSTM是RNN的一种算法, 在序列分类中比较有用。常用于语音识别,文字处理(NLP)等领域。 等同于VGG等CNN模型在在图像识别领域的位置。

LSTM模型网络结构相对更加复杂,下面是一个lstm_mnist网络的例子,用netron分析结构如下,是不是比上面CNN的复杂好多?

手搓Lenet的keras实现_第15张图片

具体参考下篇文章:

用LSTM分类 MNIST - hylas - 博客园


FAQS:

 if you encunter issues below, may be you have a wrong protobuf version.

手搓Lenet的keras实现_第16张图片

as follows protobuf:

you can try down grade protbuf versions as below:

pip install protobuf~=3.19.0
pip install six~=1.15.0
pip installtyping-extensions~=3.7.4

this would be fine!


结束!

你可能感兴趣的:(人工智能,嵌入式系统,算法,keras,深度学习,机器学习)