用CNN实现手写数字识别

一、模型结构

用户输入的图像是一个784维的向量x,我们按照以下步骤搭建网络:
1、把x整形为【28, 28, 1】的灰度图
2、用一次3x3的卷积操作从x中抽象出32个基本特征,图像形状变成【28, 28, 32】,这些特征可以是特殊灰度的点,长度不超过3的短直线、曲线或者其他形状等。32种特征与32个通道一一对应。卷积操作有一个重要作用:从当前输入的图像所拥有的特征出发,抽象出高一级的特征。所以一个通道就代表了一种特征,不同通道代表不同的特征。一个通道上元素值的大小代表了对应像素点的对应特征的强弱。
3、进行第二次3x3的卷积,进一步抽象出高一级的特征。由于高级的特征往往比低级的特征多(例如不同的原子有100多个,组合成分子后就很多了),所以,我们把特征的种类从32提升到了64种。另外,设置步长的大小为2,目的就是把图像的大小变成14x14。这是因为虽然特征的种类增加了,但是特征的数量却在减少。就好比用4个原子合成了一个分子,虽然这4原子的分子有很多很多种可能,但数量却只有一个。手写数字图像最终的特征只有一个,那就是它是10个数字中的一个。
4、在进行第三次3x3卷积,形成7x7大小的128种特征。
5、最后再进行一次7x7的卷积,并且只抽象出10种特征。这10种特征分别代表了当前这个手写数字图像所具有的0-9十个数字特征的大小。特征最明显的就是答案。
用CNN实现的手写数字识别网络结构如下图:

上述每一次卷积之后都要进行激活,除了最后一次7x7卷积。这是因为卷积是线性变换,线性变换的组合仍然是线性变换,除非线性变换之后紧跟激活。如果在最后一层跟随激活的话,输出值中就没有负数,这就不能拟合任意函数了。

二、代码实战

import tensorflow._api.v2.compat.v1 as tf
tf.disable_v2_behavior()
from input_data import read_data_sets
# from tensorflow.examples.tutorials.mnist.input_data import read_data_sets
import os
from functools import reduce

# 定义模型
class Tensors:
    def __init__(self):
        self.x = tf.placeholder(tf.float32, [None, 784], 'x')
        x = tf.reshape(self.x, [-1, 28, 28, 1])
        x = tf.layers.conv2d(x, filters=32, kernel_size=3, padding='same',
                             activation=tf.nn.relu)  # 结果的形状:[-1, 28, 28, 32]
        x = tf.layers.conv2d(x, filters=64, kernel_size=3, strides=(2, 2), padding='same',
                             activation=tf.nn.relu)  # 结果的形状:[-1, 14, 14, 64]
        x = tf.layers.conv2d(x, filters=128, kernel_size=3, strides=(2, 2), padding='same',
                             activation=tf.nn.relu)  # 结果的形状:[-1, 7, 7, 128]
        x = tf.layers.conv2d(x, filters=10, kernel_size=7, padding='valid')
                                                     # 结果的形状:[-1, 1, 1, 10]
        logits = tf.reshape(x, [-1, 10])             # [-1, 10]

        # x = tf.layers.flatten(x)                       # 等价于x=tf.reshape(x, [-1, 7*7*128]
        # x = tf.layers.dense(x, units=1000, activation=tf.nn.relu)
        # x = tf.nn.dropout(x, keep_prob=0.6)
        # logits = tf.layers.dense(x, units=10)          # 全连接到[-1, 10]

        self.y = tf.placeholder(tf.int32, [None], 'y')
        y = tf.one_hot(self.y, 10)

        loss = tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=logits)
        self.loss = tf.reduce_mean(loss)

        self.lr = tf.placeholder(tf.float32, [], 'lr')
        opt = tf.train.AdamOptimizer(self.lr)
        self.train_op = opt.minimize(self.loss)

        self.y_predict = tf.argmax(logits, axis=1, output_type=tf.int32)  # [-1]
        cmp = tf.equal(self.y_predict, self.y)
        cmp = tf.cast(cmp, tf.float32)
        self.precise = tf.reduce_mean(cmp)


class Model:
    def __init__(self, save_path):
        self.save_path = save_path
        graph = tf.Graph()
        with graph.as_default():
            self.ts = Tensors()
            self.session = tf.Session(graph=graph)
            self.saver = tf.train.Saver()
            try:
                self.saver.restore(self.session, save_path)
                print('Success to restore model from', save_path)
            except:
                print('Fail to restore model from', save_path)
                self.session.run(tf.global_variables_initializer())
    # 模型训练
    def train(self, datasets, batch_size, epoches=50, lr=0.01):
        ts = self.ts
        for epoch in range(epoches):
            batches = datasets.train.num_examples // batch_size
            for batch in range(batches):
                xs, ys = datasets.train.next_batch(batch_size)
                self.session.run(ts.train_op, {ts.lr: lr, ts.x: xs, ts.y: ys})

            xs, ys = datasets.validation.next_batch(batch_size)
            loss, precise = self.session.run([ts.loss, ts.precise], {ts.x: xs, ts.y:ys})
            print('epoch:', epoch, ', loss:', loss, ', precise:', precise)
            make_dir(self.save_path)
            self.saver.save(self.session, self.save_path)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.session.close()
   # 模型验证
    def test(self, datasets, batch_size):
        ts = self.ts
        precise_sum = 0
        num = 0
        batches = datasets.test.num_examples // batch_size
        for _ in range(batches):
            xs, ys = datasets.validation.next_batch(batch_size)
            precise = self.session.run(ts.precise, {ts.x: xs, ts.y: ys})
            precise_sum += precise * len(xs)
            num += len(xs)
        print('The precise is:', precise_sum/num)

    def predict(self, xs):
        ts = self.ts
        return self.session.run(ts.y_predict, {ts.x: xs})

def make_dir(path: str):
    pos = path.rfind(os.sep)
    if pos >= 0:
        os.makedirs(path[0: pos], exist_ok=True)

def show_params(graph):
    total = 0
    for var in graph.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES):
        ps = reduce(lambda s, e: s*e.value, var.shape, 1)
        total += ps
        print(var.name, ':', var.shape, ps)
    print('-' * 50)
    print('Total:', total)


if __name__ == '__main__':
    path_and_name = 'models/p04_03/mnist'
    with Model(path_and_name) as model:
        show_params(model.session.graph)
        ds = read_data_sets('MNIST_data/')
        model.train(ds, 200, lr=0.001)
        model.test(ds, 200)

用CNN实现手写数字识别_第1张图片
当调用Saver.save(session, path)时第一个参数session是指定的会话,负责提供变量的值,第二个参数path是模型保存的位置。save()函数会在指定目录下以指定的名字保存4个文件,这些文件除了checkpoint之外名称都相同,不同的是扩展名。如下图所示:
用CNN实现手写数字识别_第2张图片
Saver.restore(session, path)方法可以帮助我们从指定的路径恢复模型。就是指从上述路径的4个文件中读取变量的值,然后用它们给session会话中指定的变量赋值。

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