第1例 基于全连接网络的手写体数字MNIST识别

1.1 环境说明

  1. Window10
  2. tensorflow2.x

1.2 理论说明

全连接网络,实质上是多层感知器,作为分类器出现在应用中。
单层感知器是一个简单的线性分类器,能够处理线性二分类问题。
多层感知器由单层感知器组合而成,能够处理非线性多分类问题。

1.2.1 单层感知器

结构如下:


计算过程的理解

  1. 求和:s = w1*x1+w2*x2+...+w3*x3
  2. 如果 s > 阈值,则输出1,否则输出0;
  3. 变换 s-阈值>0,定义bias = -阈值,称为偏置b

表达式如下:
y=f(\sum_{i=1}^{n} w_ix_i+b)
其中,w是权重,b是偏置,x是输入(特征,可以是多个),y是输出(对于2分类y是0或者1),f是激活函数。

激活函数(是一个阶跃函数)表达是如下:
f(x)= \begin{cases} 1, & \text{$x$>0} \\ 0, & \text{otherwise}\\ \end{cases}

1.2.2 多层感知器

它是由单层感知器组合形成的,后一层节点的输入来自前一层所有节点的输出,即层之间全部连接,所以叫全连接网络。网络结构图如下:


包含:输入层、隐藏层(有多个)、输出层。
其中每个蓝色点是一个神经元,每个神经元的计算和单层感知器一样。

激活函数采用了sigmoid函数,输出值范围0-1,相对于阶跃函数,它使得分类的超平面从线性到非线性。表达式如下:
f(x)=\frac {1} {1+e^x}
sigmoid的曲线:

2. 实验:手写体数字识别

2.1 实现过程分如下几个步骤

  1. 读取手写数据数据集;
  2. 构建网络:全连接神经网络;
  3. 训练模型并保存模型;
  4. 预测:输入一张手写,通过模型识别图片上的数字;

2.2 网络结构

实现手写体的网络结构大致如下:



输入一张数字图片灰度图像,图片大小28*28,包含784个像素值。
经过全连接网络后,输出10个数据(10个数据计算sortmax转为概率),即最后输出10个概率值,分别对应0-9数字图像的10类别。

2.3 数据集处理

2.3.1 数据集介绍

MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片,也包含了每一张图像的标签;MNIST数据集分为训练数据集和测试数据集两部分(数据集的数据都是由图片数据集和对应的标签数据集组成)。
其下载地址http://yann.lecun.com/exdb/mnist/
其中包含4个文件:

  1. 训练集:用于训练的数据。
    (1). train-images-idx3-ubyte.gz: 包含6万张2828图片。
    (2). train-labels-idx1-ubyte.gz:包含6万张图片的标签,即每一张是什么数字。
    2 测试集:用于测试模型的泛化能力。
    (1). t10k-images-idx3-ubyte.gz: 包含1万张28
    28图片。
    (2). t10k-labels-idx1-ubyte.gz:包含1万张图片的标签,即每一张是什么数字。

我把它解压处理内容数据如下:


2.3.2 数据集解压代码

# data_manager.py

import struct
import os
import numpy as np
import gzip

def load_images(filename):
    """load images
    filename: the name of the file containing data
    return -- a matrix containing images as row vectors
    """
    g_file = gzip.GzipFile(filename)
    data = g_file.read()
    magic, num, rows, columns = struct.unpack('>iiii', data[:16])
    dimension = rows*columns
    X = np.zeros((num,rows,columns), dtype='uint8')
    offset = 16
    for i in range(num):
        a = np.frombuffer(data, dtype=np.uint8, count=dimension, offset=offset)
        X[i] = a.reshape((rows, columns))
        offset += dimension
    return X

def load_labels(filename):
    """load labels
    filename: the name of the file containing data
    return -- a row vector containing labels
    """
    g_file = gzip.GzipFile(filename)
    data = g_file.read()
    magic, num = struct.unpack('>ii', data[:8])
    d = np.frombuffer(data,dtype=np.uint8, count=num, offset=8)
    return d

def load_data(foldername):
    """加载MINST数据集
    foldername: the name of the folder containing datasets
    return -- train_X训练数据集, train_y训练数据集对应的标签,
        test_X测试数据集, test_y测试数据集对应的标签
    """
    # filenames of datasets
    train_X_name = "train-images-idx3-ubyte.gz"
    train_y_name = "train-labels-idx1-ubyte.gz"
    test_X_name = "t10k-images-idx3-ubyte.gz"
    test_y_name = "t10k-labels-idx1-ubyte.gz"
    train_X = load_images(os.path.join(foldername, train_X_name))
    train_y = load_labels(os.path.join(foldername,train_y_name))
    test_X = load_images(os.path.join(foldername, test_X_name))
    test_y = load_labels(os.path.join(foldername, test_y_name))
    return train_X, train_y, test_X, test_y

2.4 网络结构搭建

网络这里搭建3层,2个隐层神经元分别为128和64,1个输出层神经元为类别数。

# network/mydense.py

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

class MNIST_FcNet(keras.Model):
    def __init__(self, num_classes=10):
        """ 搭建网络的层 """
        super(MNIST_FcNet, self).__init__()
        # 隐藏层第1层 -- 128个神经元,激活函数sigmoid, 这个层要有偏置
        self.fc1 = layers.Dense(128, activation="sigmoid", use_bias=True)
        # 隐藏层第2层 -- 64个神经元,激活函数sigmoid, 这个层要有偏置
        self.fc2 = layers.Dense(64, activation="sigmoid", use_bias=True)
        # 输出层 -- 64个神经元,激活函数sigmoid, 这个层要有偏置
        self.out = layers.Dense(num_classes, activation="sigmoid", use_bias=True)
        # 定义一个soft层,计算输出层的数据转为概率形式。
        self.sm = layers.Softmax()

    def call(self, x):
        """ 数据运算流程 """
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.out(x)
        x = self.sm(x)
        return x

2.5 数据集读取

按照tensorflow2.x读取batch的方式:

def process_image(image, label):
    """ 图片预处理 """
    m = image.shape[0] * image.shape[1]
    image = tf.reshape(image, (m,))
    label = tf.one_hot(label, depth=10)
    return image, label

def get_dataset(X, Y, is_shuffle=False, batch_size=64):
    ds = tf.data.Dataset.from_tensor_slices((X, Y))
    ds = ds.map(process_image)
    ds = ds.shuffle(buffer_size=1024)
    ds = ds.batch(batch_size)
    return ds

2.6 训练网络的代码

在类的初始化函数中定义好模型、优化器、损失函数等。
在train函数实现训练过程。

import os
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras import layers,losses, metrics
from data_manager import load_data
from network.mydense import MNIST_FcNet

class TrainModel():
    def __init__(self, lr=0.1):
        self.model = MNIST_FcNet(num_classes=10)            # 定义网络
        self.model.build(input_shape=(None, 784))
        self.model.summary()
        self.loss_fun = losses.CategoricalCrossentropy()    # 定义损失函数, 这里交叉熵
        self.opt = tf.optimizers.SGD(learning_rate=lr)      # 随机梯度下降优化器
        # 设定统计参数
        self.train_acc_metric = metrics.CategoricalAccuracy() 
        self.val_acc_metric = metrics.CategoricalAccuracy()

    def train(self, fpath="./data/MNIST", epochs=500, m=50):
        """ 训练网络 """
        batch_size = 64
        test_acc_list = []
        # 读取数据集
        train_X, train_y, test_X, test_y = load_data(fpath)
        train_dataset = get_dataset(train_X, train_y, is_shuffle=True, batch_size=batch_size)
        val_dataset = get_dataset(test_X, test_y, is_shuffle=False, batch_size=batch_size)
        # 训练
        loss_val = 0
        for epoch in range(epochs):
            print(" ** Start of epoch {} **".format(epoch))
            # 每次获取一个batch的数据来训练
            for nbatch, (inputs, labels) in enumerate(train_dataset):
                with tf.GradientTape() as tape:                 # 开启自动求导
                    y_pred = self.model(inputs)                 # 前向计算  
                    loss_val = self.loss_fun(labels, y_pred)    # 误差计算
                    grads = tape.gradient(loss_val, self.model.trainable_variables)         # 梯度计算
                    self.opt.apply_gradients(zip(grads, self.model.trainable_variables))    # 权重更新
                    # 更新统计传输
                    self.train_acc_metric(labels, y_pred)
                    # 打印
                    if nbatch % m == 0:
                        correct = tf.equal(tf.argmax(labels, 1), tf.argmax(y_pred, 1))
                        acc = tf.reduce_mean(tf.cast(correct, tf.float32))
                        print('{}-{} train_loss:{:.5f}, train_acc:{:.5f}'.format(epoch, nbatch, float(loss_val), acc))
            # 输出统计参数的值
            train_acc = self.train_acc_metric.result()
            self.train_acc_metric.reset_states()
            print('Training acc over epoch: {}, acc:{:.5f}'.format(epoch, float(train_acc)))
            # 每次迭代在验证集上测试一次
            for nbatch, (inputs, labels) in enumerate(val_dataset):
                y_pred = self.model(inputs)
                self.val_acc_metric(labels, y_pred)
            val_acc = self.val_acc_metric.result()
            self.val_acc_metric.reset_states()
            print('Valid acc over epoch: {}, acc:{:.5f}'.format(epoch, float(val_acc)))
            test_acc_list.append(val_acc)
            if loss_val < 0.001:
                print("*********************** loss_val: {}".format(loss_val))
                break
        # 训练完成保存模型
        tf.saved_model.save(self.model, "./output/mnist_model")
        # 画泛化能力曲线(横坐标是epoch, 测试集上的精度),并保存
        x = np.arange(1, len(test_acc_list)+1, 1)
        y = np.array(test_acc_list)
        plt.plot(x, y)
        plt.xlabel("epoch")
        plt.ylabel("val_acc")
        plt.title('model acc in valid dataset')
        plt.savefig("./output/val_acc.png", format='png')

2.7 执行训练

if __name__ == "__main__":
    path = "./output"
    if not os.path.exists(path):
        os.makedirs(path)
    model = TrainModel()
    model.train()

2.8 效果

我训练到140个批次,精度不在上升了。
训练集合上的的精度:96%,验证acc:95.5%



2.9 预测代码

# predict_model.py
import os
import numpy as np
import tensorflow as tf
import cv2
import matplotlib.pyplot as plt

def handel_image(img_dir):
    image = cv2.imread(img_dir)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 将图片转化成黑白图片
    image = cv2.resize(image, dsize=(28, 28))       # 裁剪图片成28*28大小
    x_img = np.reshape(image, [-1, 784])            # 将图像reshape:[-1,784]形式
    return x_img

if __name__ == "__main__":
    # 1. 加载模型
    model = tf.saved_model.load("./output/mnist_model")
    # 2. 图像预处理
    imag_data = handel_image("./data/test_image_300x300.png")
    # 3. 测试
    out = model(imag_data)
    cls = int(tf.argmax(out, 1))
    confidence = out[0][cls]
    print("类别: {}, 置信度:{}".format(cls, confidence))

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容