Tensorflow+CNN+Adam实现手写数字识别

手写数字识别

一. 模型训练

导入数据集ImportMINIST库

import numpy as np
import struct
import matplotlib.pyplot as plt

# 训练集文件
train_images_idx3_ubyte_file = 'dataset/train-images.idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = 'dataset/train-labels.idx1-ubyte'

# 测试集文件
test_images_idx3_ubyte_file = 'dataset/t10k-images.idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = 'dataset/t10k-labels.idx1-ubyte'


def decode_idx3_ubyte(idx3_ubyte_file):
    """
    解析idx3文件的通用函数
    :param idx3_ubyte_file: idx3文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx3_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
    offset = 0
    fmt_header = '>iiii' #因为数据结构中前4行的数据类型都是32位整型,所以采用i格式,但我们需要读取前4行数据,所以需要4个i。我们后面会看到标签集中,只使用2个ii。
    magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))

    # 解析数据集
    image_size = num_rows * num_cols
    offset += struct.calcsize(fmt_header)  #获得数据在缓存中的指针位置,从前面介绍的数据结构可以看出,读取了前4行之后,指针位置(即偏移位置offset)指向0016。
    print(offset)
    fmt_image = '>' + str(image_size) + 'B'  #图像数据像素值的类型为unsigned char型,对应的format格式为B。这里还有加上图像大小784,是为了读取784个B格式数据,如果没有则只会读取一个值(即一副图像中的一个像素值)
    print(fmt_image,offset,struct.calcsize(fmt_image))
    images = np.empty((num_images, num_rows, num_cols))
    #plt.figure()
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print('已解析 %d' % (i + 1) + '张')
            print(offset)
        images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
        #print(images[i])
        offset += struct.calcsize(fmt_image)
#        plt.imshow(images[i],'gray')
#        plt.pause(0.00001)
#        plt.show()
    #plt.show()

    return images


def decode_idx1_ubyte(idx1_ubyte_file):
    """
    解析idx1文件的通用函数
    :param idx1_ubyte_file: idx1文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx1_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数和标签数
    offset = 0
    fmt_header = '>ii'
    magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))

    # 解析数据集
    offset += struct.calcsize(fmt_header)
    fmt_image = '>B'
    labels = np.empty(num_images)
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print ('已解析 %d' % (i + 1) + '张')
        labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
        offset += struct.calcsize(fmt_image)
    return labels


def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
    """
    TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  60000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
    """
    TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  60000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)


def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
    """
    TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  10000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
    """
    TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  10000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)



if __name__ == '__main__':
    train_images = load_train_images()

    train_labels = load_train_labels()
    # test_images = load_test_images()
    # test_labels = load_test_labels()

    # 查看前十个数据及其标签以读取是否正确
    for i in range(2):
        print(train_labels[i])
        plt.imshow(train_images[i], cmap='gray')
        plt.pause(0.000001)
        plt.show()
    print('done')

借助tensorflow进行数据集训练

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense,Dropout,Convolution2D,MaxPooling2D,Flatten
from tensorflow.keras.optimizers import Adam

from ImportMINIST import *

#载入数据
test_images = load_test_images()
test_labels = load_test_labels()
train_images = load_train_images()
train_labels = load_train_labels()
print(test_images.shape)
print(test_labels.shape)

# 在tensorflow 中,在做卷积的时候需要把数据变成4 维的格式
# 这4 个维度是(数据数量,图片高度,图片宽度,图片通道数)
# 所以这里把数据reshape变成4 维数据,黑白图片的通道数是1,彩色图片通道数是3
test_images = test_images.reshape(-1, 28, 28, 1)
train_images = train_images.reshape(-1, 28, 28, 1)
print(test_images.shape)

#归一化
print(train_images[0][5])
train_images = train_images / 255
test_images = test_images / 255
print(train_images[0][5])

# 把训练集和测试集的标签转为独热编码(one-hot格式)
print(train_labels[0])
train_labels = tf.keras.utils.to_categorical(train_labels, num_classes=10)
test_labels = tf.keras.utils.to_categorical(test_labels, num_classes=10)
print(train_labels[0])

# 定义顺序模型
model = Sequential()

# 第一个卷积层
# input_shape 输入数据
# filters 滤波器个数32,生成32 张特征图
# kernel_size 卷积窗口大小5*5
# strides 步长1
# padding padding方式 same/valid
# activation 激活函数(Relu激活函数)
model.add(Convolution2D(
                         input_shape=(28, 28, 1),
                         filters=32,
                         kernel_size=5,
                         strides=1,
                         padding='same',
                         activation='relu'
                        ))

# 第一个池化层
# pool_size 池化窗口大小2*2
# strides 步长2
# padding padding方式 same/valid
model.add(MaxPooling2D(
                        pool_size=2,
                        strides=2,
                        padding='same',
                      ))

#第二个卷积层
# filters 滤波器个数64,生成64 张特征图
# kernel_size 卷积窗口大小5*5
# strides 步长1
# padding padding方式 same/valid
# activation 激活函数
model.add(Convolution2D(
                         filters = 64,
                         kernel_size = 5,
                         strides = 1,
                         padding = 'same',
                         activation = 'relu'
                        ))

#第二个池化层
# pool_size 池化窗口大小2*2
# strides 步长2
# padding padding方式 same/valid
model.add(MaxPooling2D(
                        pool_size= 2,
                        strides= 2,
                        padding= 'same'
))

# 把第二个池化层的输出进行数据扁平化
# 相当于把(64,7,7,64)数据->(64,7*7*64)
model.add(Flatten())

# 第一个全连接层
model.add(Dense(1024, activation= 'relu'))

# Dropout
model.add(Dropout(0.5))

# 第二个全连接层
model.add(Dense(10, activation='softmax'))

# 定义优化器(以Adam方式调整参数)
# leaning_rate 学习率
# 以交叉熵作为损失函数,训练过程中计算准确率
adam = Adam(learning_rate=1e-4)
model.compile(optimizer=adam, loss='categorical_crossentropy', metrics=['accuracy'])

# 训练模型
# mini-batch大小为64,训练集遍历10次
model.fit(train_images, train_labels, batch_size=64, epochs=10, validation_data=(test_images, test_labels))

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

训练效果

最终模型准确率达到了99.3%

在这里插入图片描述

二、预测自己手写的数字

对自己手写的‘8’进行预测

import tensorflow as tf
from tensorflow.keras.models import load_model
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
from ImportMINIST import *

test_images = load_test_images()
test_labels = load_test_labels()
train_images = load_train_images()
train_labels = load_train_labels()

# 获取一张照片,并把它的shape 变成二维(784->28×28),用灰度图显示
plt.imshow(train_images[15], cmap='gray')
# 不显示坐标
plt.axis('off')
plt.show()

# 载入我自己写的数字图片
img = Image.open('D:\Deep_Learning\deep-learning-from-scratch-master\deeplearning-from-scratch\Exercise\\8.jpg')
# 显示图片
plt.imshow(img)
# 不显示坐标
plt.axis('off')
plt.show()

# 把图片大小变成28×28,并且把它从3D 的彩色图变为1D 的灰度图
image = np.array(img.resize((28, 28)).convert('L'))
# 显示图片,用灰度图显示
plt.imshow(image, cmap='gray')
# 不显示坐标
plt.axis('off')
plt.show()

# 观察发现我自己写的数字是白底黑字,MNIST数据集的图片是黑底白字
# 所以我们需要先把图片从白底黑字变成黑底白字,就是255-image
# MNIST数据集的数值都是0-1 之间的,所以我们还需要/255.0 对数值进行归一化
image = (255 - image) / 255.0
# 显示图片,用灰度图显示
plt.imshow(image, cmap='gray')
# 不显示坐标
plt.axis('off')
plt.show()

# 把数据处理变成4 维数据
image = image.reshape((1, 28, 28, 1))
# 载入训练好的模型
model = load_model('DigitRecognization.h5')
# predict_classes对数据进行预测并得到它的类别
prediction = model.predict_classes(image)
print(prediction)

预测结果:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

识别正确

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值