完整项目:https://github.com/Renhongqiang/LeNet-5_MNIST/
使用MNIST数据集+Kears框架+Anaconda(python3)
项目在Anaconda自带Jupyter Notebook编写
Kears文档:https://keras.io/zh/
模型构建与训练:
加载数据集:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation,Flatten,MaxPool2D,Conv2D
from keras.optimizers import SGD, Adam, RMSprop
from keras.utils import np_utils
from keras.datasets import mnist
from pathlib import Path
#加载数据集
(train_x,train_y),(test_x,test_y)=mnist.load_data()
train_x=np.reshape(train_x,(-1,28,28,1))
print("train_x.shape:{}".format(train_x.shape))
train_y=np_utils.to_categorical(train_y,10)
print("train_y.shape:{}".format(train_y.shape))
test_x=np.reshape(test_x,(-1,28,28,1))
print("test_x.shape:{}".format(test_x.shape))
test_y=np_utils.to_categorical(test_y,10)
print("test_y.shape:{}".format(test_y.shape))
LeNet-5模型构建:
#构建模型 LeNet-5
model = Sequential()
# 6个5*5卷积核,步长为1
model.add(Conv2D(filters = 6, kernel_size = (5,5), padding='valid', strides=(1, 1), activation ='relu', input_shape = (28,28,1)))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Conv2D(filters = 16, kernel_size = (5,5), padding='valid', strides=(1, 1), activation ='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
#压扁平,为全连接做准备
model.add(Flatten())
#全连接,指定relu为激活函数
model.add(Dense(120, activation = "relu"))
#全连接,指定relu为激活函数
model.add(Dense(84, activation = "relu"))
#添加输出10个节点表示10个分类,用softmax来激活分类
model.add(Dense(10, activation = "softmax"))
模型结构:
编译模型:
#RMSprop是Geoff Hinton提出的一种自适应学习率方法,RMSprop仅仅是计算对应的平均值,因此可缓解Adagrad算法学习率下降较快的问题
optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)
# 编译 使用多类的对数损失categorical_crossentropy
model.compile(loss='categorical_crossentropy',
optimizer=optimizer,
metrics=['accuracy'])
# 输出模型结构
model.summary()
训练:
print("========开始训练============")
#训练 batch_size:每次梯度更新的样本数。如果未指定,默认为 32。 epochs:迭代次数
# verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。
history = model.fit(train_x,train_y,
batch_size=64,
epochs=30,
verbose=1,
validation_data=(test_x,test_y))
print("========训练结束============")
打印训练结果的一些分析:
# verbose: 0 或 1。日志显示模式。 0 = 安静模式,1 = 进度条
score = model.evaluate(test_x, test_y, verbose=1)
print('Val loss:', score[0])
print('Val accuracy:', score[1])
# 显示损失 准确率
fig, ax = plt.subplots(2,1)
ax[0].plot(history.history['loss'], color='b', label="Training loss")
ax[0].plot(history.history['val_loss'], color='r', label="validation loss",axes =ax[0])
ax[0].legend(loc='best', shadow=True)
ax[1].plot(history.history['acc'], color='b', label="Training accuracy")
ax[1].plot(history.history['val_acc'], color='r',label="Validation accuracy")
ax[1].legend(loc='best', shadow=True)
plt.show()
保存模型:
# 保存CNN网络结构 model_save为项目根目录中一个文件夹,会自动生成.json和.h5文件保存模型和权重
model_structure = model.to_json()
f = Path("model_save/LeNet_5model_structure_3epochs.json")
f.write_text(model_structure)
# 保存训练好的权重参数
model.save_weights("model_save/LeNet_5model_weights_3epochs.h5")
加载保存后模型进行测试:
加载相关库:
from keras.models import model_from_json
from pathlib import Path
from keras.preprocessing import image
import numpy as np
import matplotlib.pyplot as plt
import cv2
import pandas as pd
import matplotlib
%matplotlib inline
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
加载模型(model_save/model_structure_10epochs.json为训练后保存的路径):
# 将MNIST中的分类标签按照0--9的顺序表示为数组中的名称
class_labels = ["0","1","2","3","4","5","6","7","8","9"]
# 加载包含模型结构信息的json文件
# model_save/model_structure_3epochs.json
# model_save/model_weights_3epochs.h5
f = Path("model_save/LeNet_5model_structure_3epochs.json")
model_structure = f.read_text()
# 根据json文件信息重构CNN模型
model = model_from_json(model_structure)
# 加载模型预训练好的权重参数
model.load_weights("model_save/LeNet_5model_weights_10epochs.h5")
加载自己要测试的图片进行缩放与灰度化:
# 测试图片准备:
# 1、加载彩色图片缩放
# 2、灰度化彩色图片并保存为cache.png
# 3、灰度化cache.png
# 所有图片放在项目中testimg文件夹中
# 1、加载彩色图片
image_path="testimg/4_1.png"
ii = cv2.imread(image_path)
#缩放为28*28*3
ii = cv2.resize(ii, (28, 28), interpolation=cv2.INTER_CUBIC)
#2、灰度化彩色图片并保存为cache.png
#变为灰度图片
gray_image = cv2.cvtColor(ii, cv2.COLOR_BGR2GRAY)
print(gray_image.shape)
#显示灰度化后图片
plt.imshow(gray_image, 'Greys')
#去除空白保存灰度后的图片为 testimg/cach.png
plt.axis('off')
fig = plt.gcf()
fig.set_size_inches(7.0/3,7.0/3) #dpi = 300, output = 700*700 pixels
plt.gca().xaxis.set_major_locator(plt.NullLocator())
plt.gca().yaxis.set_major_locator(plt.NullLocator())
plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, hspace = 0, wspace = 0)
plt.margins(0,0)
fig.savefig('testimg/cach.png', format='png', transparent=True, dpi=300, pad_inches = 0)
#加载灰度后图片 testimg/cach.png
cach_image_path="testimg/cach.png"
cach_img = cv2.imread(cach_image_path)
#缩放为28*28*3
cach_img = cv2.resize(cach_img, (28, 28), interpolation=cv2.INTER_CUBIC)
#变为灰度图片
gray_image = cv2.cvtColor(cach_img, cv2.COLOR_BGR2GRAY)
print("二次灰度化图片:")
#显示
plt.imshow(gray_image, 'Greys')
plt.show()
预测:
#加载灰度图片
image_to_test = image.img_to_array(gray_image)
print("image_to_test.shape: {0}".format(image_to_test.shape))
# 为图片添加一个维度,变为四维数组 (1,28,28,1) (因为模型被设计为一次读入一个数据集)
list_of_images = np.expand_dims(image_to_test, axis=0)
print("list_of_images.shape: {0}".format(list_of_images.shape))
# 做预测
results = model.predict(list_of_images)
#绘制可能性柱状图
data=results[0]
plt.barh(range(10), data, height=0.8, color='steelblue', alpha=1) # 从下往上画
plt.yticks(range(10), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
plt.xlim(0,1)
plt.xlabel("可能性")
plt.title("0-1 可能性")
for x, y in enumerate(data):
plt.text(y + 0.2, x - 0.1, '%s' % y)
plt.show()
# 返回可能性最大的分类对应的下标索引值
single_result=results[0]
most_likely_class_index = int(np.argmax(single_result))
class_likelihood = single_result[most_likely_class_index]
# 返回最有可能的图片类型名称
class_label = class_labels[most_likely_class_index]
# 打印结果
print("预测结果: {0} ,可能性为: {1:2f}".format(class_label, class_likelihood))
# 显示原图
print("原图: ")
img2 = image.load_img(image_path)
image_to_array = image.img_to_array(img2)
image_to_array = image_to_array.astype('int32')
plt.imshow(image_to_array);