手写识别(一)

6 篇文章 3 订阅
5 篇文章 0 订阅

前言

  纸上得来终觉浅,绝知此事要躬行。我们已经学习了神经网络理论知识,从这节课开始试着使用FPGA来部署神经网络模型,实现手写识别项目,来看看FPGA的优势。

一、环境介绍

软件

  1. Tensorflow1.15.0
  2. PyCharm Community Edition 2021.3.1
  3. Python3.7.9
  4. opencv-python
  5. pillow
  6. scipy1.2.1
  7. numpy

二、训练模型

2.1 前向传播

  1. 编写mnist_backward.py文件
# -*- coding:utf-8 -*-
import tensorflow as tf

# 前向传播算法  两层全连接卷积网络
# 28 × 28 个像素点,表示图片的像素值
INPUT_NODE = 784
# 表示输出10个数,表示0-9出现的概率     最大概率对应的下标表示识别结果
OUTPUT_NODE = 10

# 定义隐藏层个数
# LAYER_NODE1 = 500

LAYER_NODE1 = 64


def forward(x, regularizer):  # regularizer表示正则化系数,值由 mnist_backward 模块传入的

    def get_weight(shape, regularizer):  # 生成权重矩阵 元素为服从正态分布的随机值
        # 随机生成参数,去掉偏离过大的正态分布
        w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
        # 加入正则化
        if regularizer:
            tf.add_to_collection(
                'losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
        return w

    # 设定偏置项
    def get_bias(shape):  # 生成偏置矩阵 元素为服从正态分布的随机值
        b = tf.Variable(tf.zeros(shape))
        return b

    # 由输入层到隐藏层的参数w1形状为[784,500]
    w1 = get_weight([INPUT_NODE, LAYER_NODE1], regularizer)  # 输入像素与权重进行卷积
    # 由输入层到隐藏的偏置b1形状为长度500的一维数组,
    b1 = get_bias([LAYER_NODE1])  # 偏置层
    # 前向传播结构第一层为输入 x与参数 w1矩阵相乘加上偏置 b1 ,再经过relu函数激活 ,得到隐藏层输出 y1。n
    y1 = tf.nn.relu(tf.matmul(x, w1) + b1)  # 对第一层输出进行激活

    # 由隐藏层到输出层的参数w2形状为[500,10]
    w2 = get_weight([LAYER_NODE1, OUTPUT_NODE], regularizer)  # 对第一次的结果再次卷积
    # 由隐藏层到输出的偏置b2形状为长度10的一维数组
    b2 = get_bias([OUTPUT_NODE])  # 再次偏置
    # 前向传播结构第二层为隐藏输出 y1与参 数 w2 矩阵相乘加上偏置 矩阵相乘加上偏置 b2,得到输出 y。
    # 由于输出 。由于输出 y要经过softmax oftmax 函数,使其符合概率分布,故输出y不经过 relu函数
    y = tf.matmul(y1, w2) + b2  # 输出结果  不激活

    return y

2.2 后向传播

  1. 编写mnist_forward.py文件并运行
# -*- coding:utf-8 -*-

# 后向传播算法
import os
import tensorflow as tf
from tensorflow_core.examples.tutorials.mnist import input_data

import mnist_forward

# 每轮输入的文件数量
BATCH_SIZE = 200
# 设定初始的学习率
LEARNING_RATE_BASE = 0.8  # 学习率太小,权重调整幅度太小,训练速度太慢;学习率太大,损失函数会不理想,无法收敛,出现振荡现象
# 设定学习率的衰减率
LEARNING_RATE_DECAY = 0.99  #
# 设定正则化系数
REGULARIZER = 0.0001
# 训练轮数   一共训练50000轮
STEPS = 50000
# 设定滑动平均率
MOVING_AVG_DECAY = 0.99
# 训练好的模型保存路径
MODEL_SAVE_PATH = 'models'
# 训练好的模型保存名称
MODEL_NAME = 'mnist_model'


def backward(mnist):
    # 用placeholder给输入的训练数据x和 标签y_ 占位
    x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
    y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])

    # 调用mnist_forward文件中的前向传播过程forword()函数,并设置正则化,计算训练数据集上的预测结果y
    y = mnist_forward.forward(x, REGULARIZER)

    # 当前计算轮数计数器,设定为不可训练类型
    global_step = tf.Variable(0, trainable=False)

    # global_step :全局迭代次数,是一个不可训练的tf变量,在每次的训练迭代过程中,global_step作为优化器的 minimize 函数的参数传入,
    # 并且会自动自加1,学习率 learning_rate 也会随之改变

    # 构建交叉熵
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        logits=y, labels=tf.argmax(y_, 1))
    # 构建基于交叉熵的损值函数
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    # 对损值函数进行正则化
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))

    # 设定动态学习率
    learning_rate = tf.train.exponential_decay(  # 指数衰减
        LEARNING_RATE_BASE,  # 初始学习率 0.8
        global_step,  # 总共的迭代次数
        mnist.train.num_examples / BATCH_SIZE,  # 喂入多少轮BATCH_SIZE后,更新一次学习率,此处设为:总样本数/BATCH_SIZE
        LEARNING_RATE_DECAY,  # 学习率的衰减率 0.99
        staircase=True
    )

    train_step = tf.train.GradientDescentOptimizer(
        learning_rate).minimize(loss, global_step=global_step)

    # 设定滑动平均率
    ema = tf.train.ExponentialMovingAverage(MOVING_AVG_DECAY, global_step)
    ema_op = ema.apply(tf.trainable_variables())

    # 在模型训练时引入滑动平均可以使模型测试数据上表现更加健壮
    with tf.control_dependencies([train_step, ema_op]):
        train_op = tf.no_op(name='train')

    # 初始化模型保存器
    saver = tf.train.Saver()

    with tf.Session() as sess:
        # 初始化参数
        init_op = tf.global_variables_initializer()
        sess.run(init_op)

        # 从模型路径加载已有的训练结果,恢复到当前session
        ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
        if ckpt and ckpt.model_checkpoint_path:
            saver.restore(sess, ckpt.model_checkpoint_path)

        for i in range(STEPS):
            # 读取mnist数据集 每次喂入 BATCH_SIZE 组训练数据 和 标签
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            # 进行对应的训练
            _, loss_value, learning_rate_val, step = sess.run(
                [train_op, loss, learning_rate, global_step],
                feed_dict={x: xs, y_: ys})
            if 0 == i % 1000:  # 每隔1000轮,打印一次损失函数值信息
                fmt = 'After {:05d} steps, loss is {:.09f}, learning rate is {:.09f}'
                print(fmt.format(step, loss_value, learning_rate_val))
                # 将模型进行保存
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)


def main():
    # 在main()函数中,先加载指定路径下的训练数据集,并调用backward函数进行模型训练
    mnist = input_data.read_data_sets('data', one_hot=True)
    backward(mnist)


if __name__ == '__main__':
    main()
  1. 后向传播生成的模型文件

在这里插入图片描述

2.3 测试模型

  1. 编写predict.py文件并运行
# -*- coding:utf-8 -*-


from PIL import Image
import tensorflow as tf
import numpy as np
from tensorflow_core.examples.tutorials.mnist import input_data
import mnist_backward
import mnist_forward
import time

def restore_model(pic_array):
    # 加载数据集
    mnist = input_data.read_data_sets('data', one_hot=True)

    # 利用tf.Graph()复现之前定义的计算图
    with tf.Graph().as_default() as gph:
        # 利用placeholder给训练数据 x 占位
        x = tf.placeholder(tf.float32, [None, mnist_forward.INPUT_NODE])
        # 调用mnist_forward文件中的前向传播过程forword()函数
        y = mnist_forward.forward(x, None)
        y_ = tf.placeholder(tf.float32, [None, mnist_forward.OUTPUT_NODE])
        # y的最大值对应的索引号,就是预测的数字的值
        pre_value = tf.argmax(y, 1)

        variable_avg = tf.train.ExponentialMovingAverage(mnist_backward.MOVING_AVG_DECAY)
        variable_to_restore = variable_avg.variables_to_restore()
        saver = tf.train.Saver(variable_to_restore)

        # 计算模型在测试集上的准确率
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.arg_max(y_, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

        with tf.Session() as sess:
            # 加载指定路径下的ckpt
            ckpt = tf.train.get_checkpoint_state(mnist_backward.MODEL_SAVE_PATH)
            # 若模型存在,则加载出模型到当前对话,在测试数据集上进行准确率验证,并打印出当前轮数下的准确率
            if ckpt and ckpt.model_checkpoint_path:
                saver.restore(sess, ckpt.model_checkpoint_path)
                # 预测操作
                global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
                accuracy_score = sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
                print("After %s training step(s), test accuracy = %g" % (global_step, accuracy_score))
                pre_value = sess.run(pre_value, feed_dict={x: pic_array})
                # print(pre_value)
                return pre_value


def pre_dic(pic_path, wg_bg=False):
    # 读取图片
    img = Image.open(pic_path)
    # 用消除锯齿的方式,将图片resize 为28 × 28
    reIm = img.resize((28, 28), Image.ANTIALIAS)

    # 将resize的图片转换为灰度图,并转换为矩阵的方式
    img_array = np.array(reIm.convert('L'))

    print("img_array:\n", img_array)

    # mnist训练的图片要求黑底白字,因此训练之后的模型也只接收黑底白字的图片
    # 当推测的是白底黑字的单色通道图片时,需要对图片进行反色,变成黑底白字,只留下纯白和纯黑点
    # 如果推测的是黑底白字的图片,可以不用进行反色
    # 如果推测的是rgb彩色图片,则推测功能不可用
    # 该推测代码基于白底黑字的图片,所以需要进行反色。
    if not wg_bg:
        threshold = 50
        for i in range(28):
            for j in range(28):
                img_array[i][j] = 255 - img_array[i][j]
                if img_array[i][j] < threshold:
                    # 黑点
                    img_array[i][j] = 0
                else:
                    # 白点
                    img_array[i][j] = 255
    # 将图片整理为1 × 784的矩阵
    nm_array = img_array.reshape([1, 784])
    # 转换为浮点型
    nm_array = nm_array.astype(np.float32)
    # 将rbg从0-255变为1-255的数
    img_ready = np.multiply(nm_array, 1.0 / 255.0)
    return img_ready


def application(image_path, wg_bg=False):
    pic_array = pre_dic(image_path, wg_bg=wg_bg)
    pre_val = restore_model(pic_array)
    print("predict result is", pre_val)
    # print(pre_val[0])


if __name__ == '__main__':
    # 黑底白字的图片推测
    # application('4.bmp', wg_bg=True)
    # application('2.bmp', wg_bg=True)
    # application('6.bmp', wg_bg=True)

    # application('0.png', wg_bg=False)
    # application('1.png', wg_bg=False)
    # application('2.png', wg_bg=False)
    # application('3.png', wg_bg=False)
    # application('4.png', wg_bg=False)
    # application('5.png', wg_bg=False)
    # application('7.png', wg_bg=False)
    # application('9.png', wg_bg=False)
    start = time.time()
    application('test/test_0.png', wg_bg=True)
    end = time.time() - start
    print("usage of the time is ", end, "s")
  1. 测试结果与推理时间

在这里插入图片描述
  推理结果显示,推理一张图像要使用大概0.8s,每次运行推理图像的时间略有不同。

2.4 提取图像

  1. 编写extract_mnist.py文件并运行
from tensorflow_core.examples.tutorials.mnist import input_data
from scipy import misc
import numpy as np
import os

mnist = input_data.read_data_sets('data',one_hot=True)

if not os.path.exists('train'):         #创建 train 文件夹作为训练数据集,将训练集压缩包里的图片解压到 train 文件夹中
    os.mkdir('train')
if not os.path.exists('test'):          #创建 test 文件夹作为测试数据集,将测试集压缩包里的图片解压到 test 文件夹中
    os.mkdir('test')

for (idx, img) in enumerate(mnist.train.images):        #将压缩数据集中的 训练数据图片 解压到 train 文件夹下
    img_arr = np.reshape(img, [28,28])
    misc.imsave('train/train_' + str(idx) + '.png',img_arr)

for (idx, img) in enumerate(mnist.test.images):         #将压缩数据集中的 测试数据图片 解压到 test 文件夹下
    img_arr = np.reshape(img, [28,28])
    misc.imsave('test/test_' + str(idx) + '.png',img_arr)
  1. 提取图像数据运行结果

在这里插入图片描述

三、提取参数

  高层次综合(High Level Synthesis,HLS)是使用高级语言如c,c++进行门级电路的设计,意在和verilogHDL功能一致,只是站在软件工程师的角度进行硬件电路的设计。所以本章节的提取参数就是要把神经网络模型参数转换成c/c++能够处理的文件,比如头文件。

  1. 编写read_params.py并运行,读取权重和偏置
import tensorflow as tf

if __name__ == '__main__':
    reader = tf.compat.v1.train.NewCheckpointReader('models/mnist_model-49001')
    print("reader",reader)
    all_variables = reader.get_variable_to_shape_map()
    quantized_conv_list = ['Variable', 'Variable_1', 'Variable_2', 'Variable_3']
    params = ['layer1_weight', 'layer1_bias', 'layer2_weight', 'layer2_bias']
    params_num = [784*64,64,64*10,10]
    for key, item,params_num in zip(quantized_conv_list, params,params_num):
        with open(item+".h", 'w+') as f:
            # f.write(str(reader.get_tensor(key).tolist()))
            new_str1 = str(reader.get_tensor(key).tolist())
            new_str2 = new_str1.replace('[','')
            new_str3 = new_str2.replace(']','')
            f.write("float "+str(item)+"["+str(params_num)+"]={"+new_str3+"};")
            f.close()
  1. 提取权重和偏置结果

在这里插入图片描述

  1. 编写readpic_array.py并运行,读取图像数据
from PIL import Image
import numpy as np

def pre_dic(pic_path):
    img = Image.open(pic_path)
    reIm = img.resize((28, 28), Image.ANTIALIAS)
    img_array = np.array(reIm.convert('L'))
    nm_array = img_array.reshape([1, 784])
    nm_array = nm_array.astype(np.float32)
    img_ready = np.multiply(nm_array, 1.0 / 255.0)
    return img_ready

if __name__ == '__main__':
    pic_name = [
        "test_0.png",
        "test_1.png",
        "test_2.png",
        "test_3.png",
        "test_4.png",
        "test_5.png",
        "test_6.png",
        "test_7.png",
        "test_8.png",
        "test_9.png"]

    save_pic_name = [
        "input_0",
        "input_1",
        "input_2",
        "input_3",
        "input_4",
        "input_5",
        "input_6",
        "input_7",
        "input_8",
        "input_9"
    ]

    for i in range(10):
        array = pre_dic('test/'+pic_name[i])
        with open(save_pic_name[i] + '.h', 'w') as f:
            new_str1 = str(array.tolist())
            new_str2 = new_str1.replace('[', '')
            new_str3 = new_str2.replace(']', '')
            f.write("float " + save_pic_name[i] + "[784]={" + new_str3 + "};")
            f.close()
  1. 提取图像参数运行结果

在这里插入图片描述
注意:图像一定要归一化

总结

  手写识别第一部分内容到此结束,打包好所有的头文件,为手写识别(二)课程做准备。敬请期待!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值