基于深度学习的MNIST数据集训练与手写数字识别

基于深度学习的MNIST数据集训练与手写数字识别

实验目的

通过深度学习算法对训练集数据进行训练,实现MNIST数据集的识别

实验原理

全连接神经网络

全连接神经网络是一种按照误差逆向传播算法训练的多层前馈神经网络。无论在网络理论还是在性能方面已比较成熟。其突出优点就是具有很强的非线性映射能力和柔性的网络结构。网络的中间层数、各层的神经元个数可根据具体情况任意设定,并且随着结构的差异其性能也有所不同。

全连接神经网络的计算过程由正向计算过程和反向计算过程组成。正向传播过程,输入模式从输入层经隐含层逐层处理,并转向输出层,每一层神经元的状态只影响下一层神经元的状态。如果在输出层不能得到期望的输出,则转入反向传播,将误差信号沿原来的连接通路返回,通过修改各神经元的权值,使得误差信号最小。

卷积神经网络

卷积神经网络分为卷积层块和全连接层块两个部分。下面我们分别介绍这两个模块。

卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用5x5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。卷积层块的两个最大池化层的窗口形状均为2x2 ,且步幅为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。

卷积层块的输出形状为(批量大小, 通道, 高,宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。

总的来说,卷积神经网络的结构为输入—卷积层—池化层—卷积层—池化层—全连接层—输出。卷积核里面的参数,只是一开始是随机数,它本身是需要训练的权重值,只是一开始被初始化了为随机数,并不是一直都是随机数,它会随着网络的训练,逐渐发生变化,最后生成固定的权重值。

实验环境

基于anaconda3的python环境

实验步骤

环境配置

下载pycharm

下载anaconda3并配置环境变量

创建open-mmlab的新环境

通过读.txt文件安装所需的库

在pycharm中添加解释器

搭建全连接神经网络

加载数据集

将图片数据展开成一维

搭建全连接神经网络

对网络进行编译

通过训练集数据对网络进行训练

输出结果

搭建卷积神经网络

加载数据集

权重与偏置初始化

搭建卷积函数与最大池化函数

通过训练集数据对网络进行训练

输出结果

识别手写数字

手写数字并拍照

处理数字图片使其转化为矩阵

导入训练好的神经网络中

输出结果

实验结果及分析

DNN

单隐藏层模型

首先采用较少单元数的单隐藏层全连接神经网络进行MNIST数据集的训练与识别

经过训练后,得到结果:test_loss: 0.5499270486116409 test_accuracy: 0.8608999848365784

我们注意到,由于神经网络的训练过程存在一定的随机性,故即使是相同的网络结构与训练方法所得出的识别率也有一定的波动,故我们对神经网络模型都进行了5次独立的训练,取平均值作为实验结果。后文中所得到的正确率均为相同条件下的平均值。

调整神经网络的参数

可以通过调整神经网络的参数来改变神经网络的性能

首先我们可以改变神经网络的单元数量。增加神经网络的单元数量,维持其他不变,得到结果:test_loss: 0.480959872841835 test_accuracy: 0.9620000123977661

我们也可以通过添加一个隐藏层来尝试改变神经网络的性能。在上一步的基础上,添加一个额外的隐藏层。维持其他不变,得到结果:test_loss: 0.25953738329410553 test_accuracy: 0.9664000272750854

通过对比可以发现,增加神经元数量与添加隐藏层可以一定程度上提高识别的正确率。

调整神经网络训练次数

神经网络训练次数也有一定的影响。直观来说,训练次数越多,训练效果越好。我们使用双隐藏层神经网络结构,分别将训练回合设定为5次、10次、20次、30次,得到结果如下所示

当训练回合为5次时,test_loss: 0.3545956725597382 test_accuracy: 0.9516000151634216

当训练回合为10次时,test_loss: 0.3348649759173393 test_accuracy: 0.954800009727478

当训练回合为20次时,test_loss: 0.25953738329410553 test_accuracy: 0.9664000272750854

当训练回合为30次时,test_loss: 0.2141496712207794 test_accuracy: 0.9739000201225281

由结果可知,当训练次数过少时,识别的正确率较差,随着次数的上升,正确率也会有一定程度的提高。但是当训练次数增大后,正确率并不会一直上升,这是由全连接神经网络算法的上限决定的。

CNN

CPU运行CNN

tensorflow

该部分是基于tensorflow的神经网络识别。TensorFlow 是一个端到端开源机器学习平台。它拥有一个全面而灵活的生态系统,其中包含各种工具、库和社区资源,可助力研究人员推动先进机器学习技术的发展,并使开发者能够轻松地构建和部署由机器学习提供支持的应用。tensorflow支持CPU与GPU运行,这里我们使用调用CPU的tensorflow框架搭建CNN神经网络

少量次数的CNN训练

修改训练回合可以提高正确率。如果我们将一个训练回合设定为6万次对训练集数据的随机训练(数据集包含5万个手写数字图片),当我们分别将训练回合设定为1次、2次、3次、5次,得到正确率如下所示

当训练回合为1次时,test accuracy: 0.9787999987602234

当训练回合为2次时,test accuracy: 0.9818000197410583

当训练回合为3次时,test accuracy: 0.9855999946594238

大量次数的CNN训练

当训练回合为5次时,test accuracy: 0.09799999743700027

通过观察正确率的变化趋势图,发现在训练回合为4次时正确率突然下降,几乎失去了识别能力。当重新将训练回合设为5次时,该现象消失了,表现出了很好的训练结果(test accuracy: 0.9894999861717224)。继续加大训练回合到10次,此时不能实现识别。本次训练中发现在训练回合为5.7次左右时正确率突然下降,并且保持在0.1左右,失去了识别能力。重复多次进行该试验,观察到正确率下降的拐点有2.7、4、5.7、6.4

由结果可知,当训练次数过少时,识别的正确率较差,随着次数的上升,正确率也会有一定程度的提高。然而,当训练次数多到一定程度时,CNN会失去识别能力。经过查阅资料,推测可能原因是出现了过拟合现象。

比较这两种神经网络结构的正确率,总体而言,在选择正确的训练次数时,卷积神经网络的识别效果高于全连接神经网络,这是由网络的性质所决定的。

GPU运行CNN

pytorch

该部分的目的是搭建一个基于pytorch的神经网络,并识别自己手写的数字。

pytorch作为深度学习开发平台,提供很高的灵活性和速度。它具有高超的简洁性、强大的动态计算能力、活跃的社区以及相当广泛的受众。pytorch和TensorFlow最重要的一点区别就是在pytorch中是动态图机制 ,而在TensorFlow中是静态图机制。在 TensorFlow 中,在跑模型之前会静态的定义图形。和外界的所有联系都是通过 tf.Session 对象和 tf.Placeholder,它们都是会在模型运行被外部数据取代的张量。在PyTorch中,会更动态一些:你可以随着进展定义、更改和执行节点,没有特殊的会话界面或占位符。整体来看,PyTorch和Python结合的更紧凑些。

GPU相比较与CPU,有以下几个区别:

  • 相比CPU,GPU有数倍的带宽、相似的单精度算力、十倍的半精度算力,因此通用场景下GPU性能优势较大

  • 只使用单精度的场景,顶级CPU在正确配置的前提下可以接近GPU的性能

  • 对内存大小有要求的场景,只能使用CPU

    在本实验中,对于结构相似的CNN神经网络,调用GPU后速度明显上升,达到了预期期望。

手写数字的识别

首先是手写数字的处理。在白纸上用黑笔写下数字,经过裁剪、黑白、像素化处理,得到一个28*28的图片。通过opencv中的cv2.inRange函数可以实现颜色抓取,得到每一个像素点的颜色数据。我们可以设置一个boundaries数组,用以界定色块的具体值。进而我们可以将所得数据存进一个28x28的矩阵中,该矩阵全部由0、1组成。将该数据导入训练好的pytorch框架中,即可实现对手写数字的识别。在本次实验中,共手写了10个数字,均正确识别出来。

附录

DNN代码

import warnings
warnings.filterwarnings('ignore')
from keras.utils import to_categorical
from keras import models, layers, regularizers
from keras.optimizers import RMSprop
from keras.datasets import mnist
import matplotlib.pyplot as plt
​
# 加载数据集
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# print('训练集大小', train_images.shape, '测试集大小', test_images.shape)
# print(train_images[1])
# print('该图片中的数字为', train_labels[1])
# plt.imshow(train_images[1])
# plt.show()
​
# 将图片由二维展开成一维
train_images = train_images.reshape((60000, 28*28)).astype('float')
test_images = test_images.reshape((10000, 28*28)).astype('float')
# print(train_images[1])
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
# print('该图片中的数字为', train_labels[1])
​
# 搭建一个全连接神经网络
# network = models.Sequential()
# network.add(layers.Dense(units=15, activation='relu', input_shape=(28*28, ),))
# network.add(layers.Dense(units=10, activation='softmax'))
network = models.Sequential()
network.add(layers.Dense(units=128, activation='relu', input_shape=(28*28, ),
                         kernel_regularizer=regularizers.l1(0.0001)))
network.add(layers.Dropout(0.01))
network.add(layers.Dense(units=32, activation='relu',
                         kernel_regularizer=regularizers.l1(0.0001)))
network.add(layers.Dropout(0.01))
network.add(layers.Dense(units=10, activation='softmax'))
​
# 查看网络结构
# print(network.summary())
​
# 编译步骤,RMS prop是一种优化算法,categorical_crossentropy损失函数,
network.compile(optimizer=RMSprop(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
​
# 训练网络,用fit函数, epochs表示将全部训练样本训练多少个回合, batch_size表示每次训练给多少样本数,verbose = 2 为每个epoch输出一行记录
network.fit(train_images, train_labels, epochs=30, batch_size=128, verbose=2)
​
# 在测试集上测试该模型的性能
# pre = network.predict(test_images[:3])
# print(pre, test_labels[:3])
test_loss, test_accuracy = network.evaluate(test_images, test_labels)
print("test_loss:", test_loss, "    test_accuracy:", test_accuracy)

CNN代码

tensorflow

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import pylab
import os
import time
​
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'#去除红色警告
​
mnist = input_data.read_data_sets("mnist_data", one_hot=True)#使用TensorFlow远程下载MNIST数据集,并且保存到当前文件夹下的mnist_data文件夹
'''
代码中的one_hot=True,表示将样本标签转化为one_hot编码。
举例来解释one_hot编码:假如一共有10类:0的one_hot为1000000000,1的ont_hot为0100000000,
2的one_hot为0010000000,依次类推。只要有一个位为1,1所在的位置就代表着第几类。
'''
​
#下面的代码可以打印出训练、测试、验证各数据集的数据和对应的标签数据
print('训练数据:',mnist.train.images)
print(mnist.train.images.shape)
print('测试数据:',mnist.test.images)
print(mnist.test.images.shape)
print('验证数据:',mnist.validation.images)
print(mnist.validation.images.shape)
print('训练集标签:',mnist.train.labels)
print(mnist.train.labels.shape)
print('测试集标签:',mnist.test.labels)
print(mnist.test.labels.shape)
print('验证集标签:',mnist.validation.labels)
print(mnist.validation.labels.shape)
​
# #打印一幅训练集中的图片看一看
# image = mnist.train.images[0]#读出第一幅图片数据1*784
# image = image.reshape(-1,28)#重塑成28*28的像素矩阵
# pylab.imshow(image)
# pylab.show()
​
​
sess = tf.InteractiveSession()
​
​
# 1、权重初始化,偏置初始化
def weights(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)
def bias(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)
​
#2、卷积函数和最大池化函数
'''
tf.nn.conv2d是Tensorflow中的二维卷积函数,参数x是输入,w是卷积的参数
strides代表卷积模块移动的步长,都是1代表会不遗漏地划过图片的每一个点,padding代表边界的处理方式
padding = 'SAME',表示padding后卷积的图与原图尺寸一致,激活函数relu()
tf.nn.max_pool是Tensorflow中的最大池化函数,这里使用2 * 2 的最大池化,即将2 * 2 的像素降为1 * 1的像素
最大池化会保留原像素块中灰度值最高的那一个像素,即保留最显著的特征,因为希望整体缩小图片尺寸
ksize:池化窗口的大小,取一个四维向量,一般是[1,height,width,1]
因为我们不想再batch和channel上做池化,一般也是[1,stride,stride,1]
'''
def conv2d(x, W):
    return tf.nn.conv2d(input=x, filter=W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
'''
这里的x,y_并不是特定的值,它们只是一个占位符,可以在TensorFlow运行某一计算时根据该占位符输入具体的值
输入图片x是一个2维的浮点数张量,这里分配给它的shape为[None, 784],784是一张展平的MNIST图片的维度
None 表示其值的大小不定,在这里作为第1个维度值,用以指代batch的大小,
输出类别y也是一个2维张量,其中每一行为一个10维的one_hot向量,用于代表某一MNIST图片的类别
'''
x = tf.placeholder(tf.float32, [None,784])
y = tf.placeholder(tf.float32,[None,10])
x_image = tf.reshape(x,[-1,28,28,1])#输入的图片整形成像素
​
# 1st layer: conv+relu+max_pool
w_conv1 = weights([5, 5, 1, 6])
b_conv1 = bias([6])
h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1)+b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
​
# 2nd layer: conv+relu+max_pool
w_conv2 = weights([5, 5, 6, 16])
b_conv2 = bias([16])
h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2)+b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*16])
​
# 3rd layer: 3*full connection(convolution)
w_fc1 = weights([7*7*16, 120])
b_fc1 = bias([120])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, w_fc1)+b_fc1)
​
# 4rd layer:full connection
w_fc2 = weights([120, 84])
b_fc2 = bias([84])
h_fc2 = tf.nn.relu(tf.matmul(h_fc1, w_fc2)+b_fc2)
​
# dropout是在神经网络里面使用的方法,以此来防止过拟合
# 用一个placeholder来代表一个神经元的输出
# tf.nn.dropout操作除了可以屏蔽神经元的输出外,
# 还会自动处理神经元输出值的scale,所以用dropout的时候可以不用考虑scale
keep_prob = tf.placeholder(tf.float32, name="keep_prob")
h_fc1_drop = tf.nn.dropout(h_fc2, keep_prob)
​
​
# 5rd layer:Output full connection
w_fc3 = weights([84, 10])
b_fc3 = bias([10])
h_fc3 = tf.nn.softmax(tf.matmul(h_fc1_drop, w_fc3)+b_fc3)
​
cross_entropy = -tf.reduce_sum(y*tf.log(h_fc3))
train_step = tf.train.AdamOptimizer(1e-3).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(h_fc3, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
​
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver(max_to_keep=1)#保存最后一个模型
X = []
Y = []
​
#开始训练
t1 = time.time()
print('==================Start Training=================')
for i in range(3000):
    batch = mnist.train.next_batch(60)
    if i%100 == 0:
        train_accuracy = accuracy.eval(session=sess, feed_dict={x: batch[0], y: batch[1],keep_prob:1.0})
        print('step {}, training accuracy: {}'.format(i, train_accuracy))
        X.append(60*i)
        Y.append(train_accuracy)
    train_step.run(session=sess, feed_dict={x: batch[0], y: batch[1],keep_prob:0.5})
print('test accuracy: {}'.format(accuracy.eval(session=sess, feed_dict={x: mnist.test.images, y:mnist.test.labels,keep_prob:1.0})))
model_path = "Model/LeNet-5.ckpt"
saver.save(sess,model_path)#保存模型于文件夹Model
t2 = time.time()
print('==================Finish Saving Model=================')
print('==================Finish Training=================')
print('==================Took Time:{}s================='.format(t2-t1))
​
#画出训练曲线
plt.plot(X, Y, c='r', linewidth=1)
plt.scatter(X, Y,  c='b', marker='^', linewidths=1)
plt.title(u"LeNet-5训练准确率", fontproperties='SimHei')
plt.xlabel("Train_Num")
plt.ylabel("Accuracy")
plt.legend()
plt.show()
​
python

pytorch

import torch
import torchvision
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
​
n_epochs = 3
batch_size_train = 64
batch_size_test = 1000
learning_rate = 0.01
momentum = 0.5
log_interval = 10
random_seed = 1
torch.manual_seed(random_seed)
​
train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=True, download=True,
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_train, shuffle=True)
test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=False, download=True,
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_test, shuffle=True)
​
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
# print(example_targets)
# print(example_data.shape)
​
fig = plt.figure()
for i in range(6):
    plt.subplot(2, 3, i + 1)
    plt.tight_layout()
    plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
    plt.title("Ground Truth: {}".format(example_targets[i]))
    plt.xticks([])
    plt.yticks([])
plt.show()
​
​
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)
​
    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)
​
​
network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)
​
train_losses = []
train_counter = []
test_losses = []
test_counter = [i * len(train_loader.dataset) for i in range(n_epochs + 1)]
​
​
def train(epoch):
    network.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = network(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch, batch_idx * len(data),
                                                                           len(train_loader.dataset),
                                                                           100. * batch_idx / len(train_loader),
                                                                           loss.item()))
            train_losses.append(loss.item())
            train_counter.append((batch_idx * 64) + ((epoch - 1) * len(train_loader.dataset)))
            torch.save(network.state_dict(), './model.pth')
            torch.save(optimizer.state_dict(), './optimizer.pth')
​
​
def test():
    network.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = network(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.data.max(1, keepdim=True)[1]
            correct += pred.eq(target.data.view_as(pred)).sum()
    test_loss /= len(test_loader.dataset)
    test_losses.append(test_loss)
    print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))
​
​
train(1)
​
test()  # 不加这个,后面画图就会报错:x and y must be the same size
for epoch in range(1, n_epochs + 1):
    train(epoch)
    test()
​
fig = plt.figure()
plt.plot(train_counter, train_losses, color='blue')
plt.scatter(test_counter, test_losses, color='red')
plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.xlabel('number of training examples seen')
plt.ylabel('negative log likelihood loss')
​
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
with torch.no_grad():
    output = network(example_data)
fig = plt.figure()
for i in range(6):
    plt.subplot(2, 3, i + 1)
    plt.tight_layout()
    plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
    plt.title("Prediction: {}".format(output.data.max(1, keepdim=True)[1][i].item()))
    plt.xticks([])
    plt.yticks([])
plt.show()
​
# ----------------------------------------------------------- #
​
continued_network = Net()
continued_optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)
​
network_state_dict = torch.load('model.pth')
continued_network.load_state_dict(network_state_dict)
optimizer_state_dict = torch.load('optimizer.pth')
continued_optimizer.load_state_dict(optimizer_state_dict)
​
for i in range(4, 9):
    test_counter.append(i * len(train_loader.dataset))
    train(i)
    test()
​
fig = plt.figure()
plt.plot(train_counter, train_losses, color='blue')
plt.scatter(test_counter, test_losses, color='red')
plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.xlabel('number of training examples seen')
plt.ylabel('negative log likelihood loss')
plt.show()

你可能感兴趣的:(深度学习,神经网络,cnn)