【深度学习实战—1】:基于Keras的手写数字识别(非常详细、代码开源)

✨博客主页:王乐予🎈
✨年轻人要:Living for the moment(活在当下)!💪
🏆推荐专栏:【图像处理】【千锤百炼Python】【深度学习】【排序算法


   本来想着多更新一些关于深度学习的文章,但这方面知识专业度很高,如果作者本身都掌握不好,又怎么能写出好文章分享呢?

  距离第一篇关于深度学习的文章:深度学习笔记1——激活函数,已经过去了9个多月,在沉淀了9个月后,这次写出了第二篇关于深度学习的文章,而且出于快速上手代码编写的目的,这次直接进行手写数字识别的实战,且看下文:

😺一、准备工作

设备\库型号\版本
显卡GTX1650
驱动程序版本457.49
tensorflow-gpu版本2.4.0
keras版本2.4.3
Python版本3.7.3

😺二、下载MNIST数据集

Keras已经对MNIST数据集做了集成,可以直接通过API下载与使用。

🐶2.1 导入所需的库与模块

from keras.datasets import mnist
import matplotlib.pyplot as plt

🐶2.2 下载数据集

# x_train_original和y_train_original代表训练集的图像与标签, x_test_original与y_test_original代表测试集的图像与标签
(x_train_original, y_train_original), (x_test_original, y_test_original) = mnist.load_data()

下载好的数据集系统会存放在C盘用户下的.keras中的datasets文件夹下:

C:\Users\Lenovo\.keras\datasets

在这里插入图片描述

😺三、数据集可视化

🐶3.1 单张图像可视化

def mnist_visualize_single(mode, idx):
    if mode == 0:
        plt.imshow(x_train_original[idx], cmap=plt.get_cmap('gray'))
        title = 'label=' + str(y_train_original[idx])
        plt.title(title)
        plt.xticks([])
        plt.yticks([])
        plt.show()
    else:
        plt.imshow(x_test_original[idx], cmap=plt.get_cmap('gray'))
        title = 'label=' + str(y_test_original[idx])
        plt.title(title)
        plt.xticks([])
        plt.yticks([])
        plt.show()

我们调用这个函数

mnist_visualize_single(mode=0, idx=0)

在这里插入图片描述
可以看到,训练数据的第一张图像是5。

🐶3.2 多张图像可视化

def mnist_visualize_multiple(mode, start, end, length, width):
    if mode == 0:
        for i in range(start, end):
            plt.subplot(length, width, 1 + i)
            plt.imshow(x_train_original[i], cmap=plt.get_cmap('gray'))
            title = 'label=' + str(y_train_original[i])
            plt.title(title)
            plt.xticks([])
            plt.yticks([])
        plt.show()
    else:
        for i in range(start, end):
            plt.subplot(length, width, 1 + i)
            plt.imshow(x_test_original[i], cmap=plt.get_cmap('gray'))
            title = 'label=' + str(y_test_original[i])
            plt.title(title)
            plt.xticks([])
            plt.yticks([])
        plt.show()

我们调用这个函数

mnist_visualize_multiple(mode=0, start=0, end=4, length=2, width=2)

在这里插入图片描述
可以看到,训练数据的前四张图像分别是5、0、4、1。

🐶3.3 原始数据量可视化

print('训练集图像的尺寸:', x_train_original.shape)
print('训练集标签的尺寸:', y_train_original.shape)
print('测试集图像的尺寸:', x_test_original.shape)
print('测试集标签的尺寸:', y_test_original.shape)

程序运行结果:

训练集图像的尺寸: (60000, 28, 28)
训练集标签的尺寸: (60000,)
测试集图像的尺寸: (10000, 28, 28)
测试集标签的尺寸: (10000,)

从这里可以知道,MNIST数据集是(28×28)的灰度图像

😺四、数据预处理

🐶4.1 验证集分配

我们从训练集的60000张图像中,分离出10000张图像用作验证集。

x_val = x_train_original[50000:]
y_val = y_train_original[50000:]
x_train = x_train_original[:50000]
y_train = y_train_original[:50000]
# 打印验证集数据量
print('验证集图像的尺寸:', x_val.shape)
print('验证集标签的尺寸:', y_val.shape)

程序运行结果:

验证集图像的尺寸: (10000, 28, 28)
验证集标签的尺寸: (10000,)

🐶4.2 图像数据预处理

我们需要先将图像转换为四维矩阵用于网络训练,且需要把图像类型从Uint8转化为float32,提高训练精度。

x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32')
x_val = x_val.reshape(x_val.shape[0], 28, 28, 1).astype('float32')
x_test = x_test_original.reshape(x_test_original.shape[0], 28, 28, 1).astype('float32')

原始图像数据的像素灰度值范围是0-255,为了提高模型的训练精度,通常将数值归一化至0-1。

x_train = x_train / 255
x_val = x_val / 255
x_test = x_test / 255

我们打印一下数据集传入网络的尺寸:

print('训练集传入网络的图像尺寸:', x_train.shape)
print('验证集传入网络的图像尺寸:', x_val.shape)
print('测试集传入网络的图像尺寸:', x_test.shape)

打印结果为:

训练集传入网络的图像尺寸: (50000, 28, 28, 1)
验证集传入网络的图像尺寸: (10000, 28, 28, 1)
测试集传入网络的图像尺寸: (10000, 28, 28, 1)

😺五、构建网络

🐶5.1 导入所需的库与模块

import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix
import seaborn as sns
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras.utils import np_utils
from keras.utils.vis_utils import plot_model

🐶5.2 定义网络模型

我们使用CNN模型做手写数字的分类,当然也可以使用感知机。

模型的构建采用序贯模型结构,网络由卷积层-池化层-卷积层-池化层-平铺层-全连接层-全连接层组成。最后一层的全连接层采用softmax激活函数做10分类。

def CNN_model():

    model = Sequential()
    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Conv2D(filters=32, kernel_size=(5, 5), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Flatten())
    model.add(Dense(100, activation='relu'))
    model.add(Dense(10, activation='softmax'))

    print(model.summary())
    return model
    
model = CNN_model()

🐶5.3 网络模型参数可视化

我们使用print(model.summary())得到神经网路的每一层参数。

print(model.summary())

输出结果为:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 24, 24, 16)        416       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 12, 12, 16)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 8, 8, 32)          12832     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 4, 4, 32)          0         
_________________________________________________________________
flatten (Flatten)            (None, 512)               0         
_________________________________________________________________
dense (Dense)                (None, 100)               51300     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1010      
=================================================================
Total params: 65,558
Trainable params: 65,558
Non-trainable params: 0
_________________________________________________________________

🐶5.4 网络结构可视化

为了更直观的展示网络的结构,我们可以通过图的方式了解网络各层之间的关系:

plot_model(model, to_file='CNN_model.png', show_shapes=True, show_layer_names=True, rankdir='TB')
plt.figure(figsize=(10, 10))
img = plt.imread('CNN_model.png')
plt.imshow(img)
plt.axis('off')
plt.show()

在这里插入图片描述
网络结构图可以更加直观的看出网络各层关系以及张量流的传递路径,此外每个网络层的输入输出尺寸都能很好的体现。

😺六、编译训练网络

🐶6.1 编译网络

Keras通过model.compile()编译网络,其可以定义损失函数、优化器、评估指标等参数。

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

这里使用交叉熵categorical_crossentropy作为损失函数,这是最常用的多分类任务的损失函数,常搭配softmax激活函数使用。

优化器我们使用自适应矩估计:Adam,Adam简直不要太好用!!!

评价指标我们使用精度:accuracy

🐶6.2 训练网络

Keras可以通过多种函数训练网络,这里我们使用model.fit()训练网络模型,函数中可以定义训练集数据与训练集标签,验证集数据与验证集标签、训练批次、批处理大小等。

train_history = model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=20, batch_size=32, verbose=2)

这里返回值train_history中包含了训练过程的许多信息,例如训练损失和训练精度等。

接下来网络就会开始训练,训练过程如下:

Epoch 1/20
1563/1563 - 9s - loss: 0.1665 - accuracy: 0.9486 - val_loss: 0.0610 - val_accuracy: 0.9821
Epoch 2/20
1563/1563 - 6s - loss: 0.0524 - accuracy: 0.9833 - val_loss: 0.0473 - val_accuracy: 0.9849
Epoch 3/20
1563/1563 - 6s - loss: 0.0353 - accuracy: 0.9891 - val_loss: 0.0416 - val_accuracy: 0.9873
Epoch 4/20
1563/1563 - 6s - loss: 0.0293 - accuracy: 0.9908 - val_loss: 0.0354 - val_accuracy: 0.9900
Epoch 5/20
1563/1563 - 6s - loss: 0.0195 - accuracy: 0.9939 - val_loss: 0.0372 - val_accuracy: 0.9898
Epoch 6/20
1563/1563 - 6s - loss: 0.0180 - accuracy: 0.9940 - val_loss: 0.0416 - val_accuracy: 0.9876
Epoch 7/20
1563/1563 - 6s - loss: 0.0150 - accuracy: 0.9953 - val_loss: 0.0425 - val_accuracy: 0.9884
Epoch 8/20
1563/1563 - 6s - loss: 0.0108 - accuracy: 0.9961 - val_loss: 0.0365 - val_accuracy: 0.9908
Epoch 9/20
1563/1563 - 6s - loss: 0.0120 - accuracy: 0.9957 - val_loss: 0.0406 - val_accuracy: 0.9890
Epoch 10/20
1563/1563 - 6s - loss: 0.0087 - accuracy: 0.9970 - val_loss: 0.0437 - val_accuracy: 0.9893
Epoch 11/20
1563/1563 - 6s - loss: 0.0081 - accuracy: 0.9973 - val_loss: 0.0487 - val_accuracy: 0.9884
Epoch 12/20
1563/1563 - 6s - loss: 0.0067 - accuracy: 0.9978 - val_loss: 0.0470 - val_accuracy: 0.9906
Epoch 13/20
1563/1563 - 6s - loss: 0.0075 - accuracy: 0.9976 - val_loss: 0.0388 - val_accuracy: 0.9905
Epoch 14/20
1563/1563 - 6s - loss: 0.0068 - accuracy: 0.9977 - val_loss: 0.0554 - val_accuracy: 0.9897
Epoch 15/20
1563/1563 - 6s - loss: 0.0056 - accuracy: 0.9981 - val_loss: 0.0512 - val_accuracy: 0.9908
Epoch 16/20
1563/1563 - 6s - loss: 0.0058 - accuracy: 0.9981 - val_loss: 0.0479 - val_accuracy: 0.9914
Epoch 17/20
1563/1563 - 6s - loss: 0.0056 - accuracy: 0.9980 - val_loss: 0.0482 - val_accuracy: 0.9912
Epoch 18/20
1563/1563 - 6s - loss: 0.0057 - accuracy: 0.9982 - val_loss: 0.0528 - val_accuracy: 0.9904
Epoch 19/20
1563/1563 - 6s - loss: 0.0046 - accuracy: 0.9985 - val_loss: 0.0538 - val_accuracy: 0.9895
Epoch 20/20
1563/1563 - 6s - loss: 0.0038 - accuracy: 0.9986 - val_loss: 0.0581 - val_accuracy: 0.9900

🐶6.3 训练过程可视化

我们可以通过图的方式展示神经网路在训练时的损失与精度的变化,我们定义函数:

def show_train_history(train_history, train, validation):
    plt.plot(train_history.history[train])
    plt.plot(train_history.history[validation])
    plt.title('Train History')
    plt.ylabel(train)
    plt.xlabel('Epoch')
    plt.legend(['train', 'validation'], loc='best')
    plt.show()

我们调用该函数:

show_train_history(train_history, 'accuracy', 'val_accuracy')
show_train_history(train_history, 'loss', 'val_loss')

在这里插入图片描述
上图分别表示网络训练过程中在训练集与验证集上的精度与损失的变化折现图。通过折线图我们可以判断网络是否发生过拟合等情况。

🐶6.4 保存训练好的网络模型

Keras有多种保存网络模型的函数,这里我们使用model.save()

model.save('handwritten_numeral_recognition.h5')

.h5模型中包含模型结构、模型权重、模型编译信息等。

😺七、网络预测

🐶7.1 测试集预测结果

Keras通过函数model.evaluate()测试神经网路在测试集上的情况。

score = model.evaluate(x_test, y_test)

score中包含了测试集上的损失与精度信息,我们打印一下:

print('Test loss:', score[0])
print('Test accuracy:', score[1])

输出打印结果:

Test loss: 0.0538315623998642
Test accuracy: 0.9905999898910522

从结果我们可知,我们的神经网路在测试集上的精度可以达到99%,说明网络还是不错的。

我们通过model.predict()对测试集图像进行预测:

predictions = model.predict(x_test)
predictions = np.argmax(predictions, axis=1)
print('前20张图片预测结果:', predictions[:20])

前20张图像的预测结果如下:

前20张图片预测结果: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5 9 7 3 4]

🐶7.2 测试集预测结果图像可视化

我们可以像第三部分那样,构造一个函数可以既显示图片,又能显示图像预测的结果,定义以下函数:

def mnist_visualize_multiple_predict(start, end, length, width):

    for i in range(start, end):
        plt.subplot(length, width, 1 + i)
        plt.imshow(x_test_original[i], cmap=plt.get_cmap('gray'))
        title_true = 'true=' + str(y_test_original[i])
        title_prediction = ',' + 'prediction' + str(model.predict_classes(np.expand_dims(x_test[i], axis=0)))
        title = title_true + title_prediction
        plt.title(title)
        plt.xticks([])
        plt.yticks([])
    plt.show()

调用该函数:

mnist_visualize_multiple_predict(start=0, end=9, length=3, width=3)

在这里插入图片描述
我们可以得到测试集上图像的真实标签与预测标签。

第9张图像已经很抽象了,网络仍能精准判别出数字是5。

🐶7.3 显示混淆矩阵

通过建立混淆矩阵可以更直观的感知每一种类别的误差。
首先构造混淆矩阵:

cm = confusion_matrix(y_test_original, predictions)
cm = pd.DataFrame(cm)

然后我们构造一个类名:

class_names = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

最后我们定义混淆矩阵可视化函数:

def plot_confusion_matrix(cm):
    plt.figure(figsize=(10, 10))
    sns.heatmap(cm, cmap='Oranges', linecolor='black', linewidth=1, annot=True, fmt='', xticklabels=class_names, yticklabels=class_names)
    plt.xlabel("Predicted")
    plt.ylabel("Actual")
    plt.title("Confusion Matrix")
    plt.show()

我们调用这个函数:

plot_confusion_matrix(cm)

可视化结果如图:
在这里插入图片描述

😺八、完整程序

from keras.datasets import mnist
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix
import seaborn as sns
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras.utils import np_utils
from keras.utils.vis_utils import plot_model
import tensorflow as tf
import os
import keras
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.compat.v1.Session(config=config)

# 设定随机数种子,使得每个网络层的权重初始化一致
# np.random.seed(10)

# x_train_original和y_train_original代表训练集的图像与标签, x_test_original与y_test_original代表测试集的图像与标签
(x_train_original, y_train_original), (x_test_original, y_test_original) = mnist.load_data()

"""
数据可视化
"""

# 单张图像可视化
def mnist_visualize_single(mode, idx):
    if mode == 0:
        plt.imshow(x_train_original[idx], cmap=plt.get_cmap('gray'))
        title = 'label=' + str(y_train_original[idx])
        plt.title(title)
        plt.xticks([])  # 不显示x轴
        plt.yticks([])  # 不显示y轴
        plt.show()
    else:
        plt.imshow(x_test_original[idx], cmap=plt.get_cmap('gray'))
        title = 'label=' + str(y_test_original[idx])
        plt.title(title)
        plt.xticks([])  # 不显示x轴
        plt.yticks([])  # 不显示y轴
        plt.show()

# 多张图像可视化
def mnist_visualize_multiple(mode, start, end, length, width):
    if mode == 0:
        for i in range(start, end):
            plt.subplot(length, width, 1 + i)
            plt.imshow(x_train_original[i], cmap=plt.get_cmap('gray'))
            title = 'label=' + str(y_train_original[i])
            plt.title(title)
            plt.xticks([])
            plt.yticks([])
        plt.show()
    else:
        for i in range(start, end):
            plt.subplot(length, width, 1 + i)
            plt.imshow(x_test_original[i], cmap=plt.get_cmap('gray'))
            title = 'label=' + str(y_test_original[i])
            plt.title(title)
            plt.xticks([])
            plt.yticks([])
        plt.show()

# 原始数据量可视化
print('训练集图像的尺寸:', x_train_original.shape)
print('训练集标签的尺寸:', y_train_original.shape)
print('测试集图像的尺寸:', x_test_original.shape)
print('测试集标签的尺寸:', y_test_original.shape)

"""
数据预处理
"""

# 从训练集中分配验证集
x_val = x_train_original[50000:]
y_val = y_train_original[50000:]
x_train = x_train_original[:50000]
y_train = y_train_original[:50000]
# 打印验证集数据量
print('验证集图像的尺寸:', x_val.shape)
print('验证集标签的尺寸:', y_val.shape)
print('======================')

# 将图像转换为四维矩阵(nums,rows,cols,channels), 这里把数据从unint类型转化为float32类型, 提高训练精度。
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32')
x_val = x_val.reshape(x_val.shape[0], 28, 28, 1).astype('float32')
x_test = x_test_original.reshape(x_test_original.shape[0], 28, 28, 1).astype('float32')

#原始图像的像素灰度值为0-255,为了提高模型的训练精度,通常将数值归一化映射到0-1。
x_train = x_train / 255
x_val = x_val / 255
x_test = x_test / 255

print('训练集传入网络的图像尺寸:', x_train.shape)
print('验证集传入网络的图像尺寸:', x_val.shape)
print('测试集传入网络的图像尺寸:', x_test.shape)

# 图像标签一共有10个类别即0-9,这里将其转化为独热编码(One-hot)向量
y_train = np_utils.to_categorical(y_train)
y_val = np_utils.to_categorical(y_val)
y_test = np_utils.to_categorical(y_test_original)

"""
定义网络模型
"""

def CNN_model():

    model = Sequential()
    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Conv2D(filters=32, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    model.add(Flatten())
    model.add(Dense(100, activation='relu'))
    model.add(Dense(10, activation='softmax'))

    print(model.summary())
    return model

"""
训练网络
"""

model = CNN_model()

# 模型网络结构图输出
plot_model(model, to_file='CNN_model.png', show_shapes=True, show_layer_names=True, rankdir='TB')
plt.figure(figsize=(10, 10))
img = plt.imread('CNN_model.png')
plt.imshow(img)
plt.axis('off')
plt.show()

# 编译网络(定义损失函数、优化器、评估指标)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

# 开始网络训练(定义训练数据与验证数据、定义训练代数,定义训练批大小)
train_history = model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=20, batch_size=32, verbose=2)

# 模型保存
model.save('handwritten_numeral_recognition.h5')

# 定义训练过程可视化函数(训练集损失、验证集损失、训练集精度、验证集精度)
def show_train_history(train_history, train, validation):
    plt.plot(train_history.history[train])
    plt.plot(train_history.history[validation])
    plt.title('Train History')
    plt.ylabel(train)
    plt.xlabel('Epoch')
    plt.legend(['train', 'validation'], loc='best')
    plt.show()

show_train_history(train_history, 'accuracy', 'val_accuracy')
show_train_history(train_history, 'loss', 'val_loss')

# 输出网络在测试集上的损失与精度
score = model.evaluate(x_test, y_test)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

# 测试集结果预测
predictions = model.predict(x_test)
predictions = np.argmax(predictions, axis=1)
print('前20张图片预测结果:', predictions[:20])

# 预测结果图像可视化
def mnist_visualize_multiple_predict(start, end, length, width):

    for i in range(start, end):
        plt.subplot(length, width, 1 + i)
        plt.imshow(x_test_original[i], cmap=plt.get_cmap('gray'))
        title_true = 'true=' + str(y_test_original[i])
        title_prediction = ',' + 'prediction' + str(model.predict_classes(np.expand_dims(x_test[i], axis=0)))
        title = title_true + title_prediction
        plt.title(title)
        plt.xticks([])
        plt.yticks([])
    plt.show()

mnist_visualize_multiple_predict(start=0, end=9, length=3, width=3)

# 混淆矩阵
cm = confusion_matrix(y_test_original, predictions)
cm = pd.DataFrame(cm)
class_names = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

def plot_confusion_matrix(cm):
    plt.figure(figsize=(10, 10))
    sns.heatmap(cm, cmap='Oranges', linecolor='black', linewidth=1, annot=True, fmt='', xticklabels=class_names, yticklabels=class_names)
    plt.xlabel("Predicted")
    plt.ylabel("Actual")
    plt.title("Confusion Matrix")
    plt.show()
    
plot_confusion_matrix(cm)

😺九、总结

  到此,深度学习的第二篇博文就完成了,这也是我写的第一个keras实战博文,从构思到跑程序再到完成这篇博文用了整整4个小时,肩膀已经巨酸,要去休息加干饭。

  其实有很多知识点限于篇幅原因没有详细介绍。例如Adam优化器为什么常用?为什么使用独热编码等,如果有不懂的小伙伴可以私聊我,或者有机会我会继续介绍相关知识。

  博主水平有限,如果有不对的地方还望多多指教,最后希望这篇文章可以帮助到任何一位想进入深度学习的伙伴!!!

  • 57
    点赞
  • 304
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王乐予

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值