返回主目录

返回 CNN 卷积神经网络目录

上一章:深度篇—— CNN 卷积神经网络(三) 关于 ROI pooling 和 ROI Align 与 插值

本小节,细说 使用 tf cnn 进行 mnist 手写数字 代码演示项目

本项目 github 代码:https://github.com/wandaoyi/tf_cnn_mnist_pro

五. TF_CNN_MNIST 手写数字代码演示

(1). 前言

之前在 深度篇——神经网络 我们学了 ANN 和 DNN,现在,我们又学了 CNN,对于学以致用来说,我们将使用 CNN 来构建 卷积神经网络。

(2). 明确需求

项目需求,就是想把 手写 的 0 ~ 9 的阿拉伯数字图片识别出来。比方说,发票上面的数字(第一个做手写数字识别的,是1989年,美国的一家银行 聘请大佬写的,当时是用卷积神经网络技术 LeNet-5 写的。前面 深度篇——神经网络(七) 细说 DNN神经网络手写数字代码演示 已经使用 DNN 案例讲解了;现在,我们就使用 LeNet-5 做一个案例讲解。当时这个项目是用来识别支票上面签约的数字 )。训练网络,当然离不开数据,所以,我们先下载数据,数据为已经为大家上传到百度云盘:链接:https://pan.baidu.com/s/13OokGc0h3F5rGrxuSLYj9Q   提取码:qfj6  。

(3). 构建项目

项目结构如下:

上面的模型,是我随意训练 10 个 epoch 得到的精度:0.984000。之前我们使用 DNN 的时候,10 个 epoch 才 0.96+ 的精度。这样,我们看到,精度上升了 2 个百分点,这样说,也许有人会不以为意,觉得并不怎么样。但是,如果我们反过来看,可以看成,错误率减少了一半,这样,效果就会非常可观了。在公司做项目的时候,会做事很重要,但是,语言表达能力,也很重要。

(4). 环境依赖

环境依赖:

pip install numpy==1.16
pip install easydict
conda install tensorflow-gpu==1.13.1 # 建议不要用 2.0 版本的 tf,坑多

tensorflow 的安装,我前面的博客有详细解说:碎点篇——tensorflow gpu 版本安装  如果不会安装的,可以查看如何安装。

README.md 文件:

# tf_cnn_mnist_pro
tf_cnn 手写数字预测 2020-02-09
- 项目下载地址:https://github.com/wandaoyi/tf_cnn_mnist_pro
- 请到百度云盘下载项目所需要的训练数据:
- 链接:https://pan.baidu.com/s/13OokGc0h3F5rGrxuSLYj9Q   提取码:qfj6 ## 参数设置
- 在训练或预测之前,我们要先进行参数设置
- 打开 config.py 文件,对其中的参数或路径进行设置。## 模型
- 模型代码 model_net.py
- 在这里,使用了 lenet-5 网络模型来提取特征## 训练模型
- 运行 cnn_mnist_train.py ,简单操作,右键直接 run
- 训练效果如下:
- acc_train: 1.0
- epoch: 10, acc_test: 0.984000
- 下面是随意训练的效果,如果想效果好,可以多训练多点epoch
- 也可以自己添加 early-stopping 进去,不麻烦的## 预测
- 运行 cnn_mnist_test.py ,简单操作,右键直接 run
- 运行后,部分预测结果会打印在控制台上
- 预测效果如下:
- 预测值: [7 2 1 0 4]
- 真实值: [7 2 1 0 4]## tensorboard 日志
- 使用 tensorboard 的好处是,这个日志是实时的,可以一边训练一边看效果图。
- 在 cmd 命令窗口,输入下面命令:
- tensorboard --logdir=G:\work_space\python_space\pro2018_space\wandao\mnist_pro\logs\mnist_log_train --host=localhost
- 在 --logdir= 后面是日志的文件夹路径,
- 在 --host= 是用来指定 ip 的,如果不写,则只能电脑的地址,而不能使用 localhost
- 在 谷歌浏览器 上打开 tensorboard 日志: http://localhost:6006/- 模型 acc
![image](./docs/images/acc.png)
- 模型结构
![image](./docs/images/graphs.png)

下面的文件或代码,里面,都有注释

(5). 配置文件 config.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/08 19:23
# @Author   : WanDaoYi
# @FileName : config.py
# ============================================from easydict import EasyDict as edict
import os__C = edict()cfg = __C# common options 公共配置文件
__C.COMMON = edict()
# windows 获取文件绝对路径, 方便 windows 在黑窗口 运行项目
__C.COMMON.BASE_PATH = os.path.abspath(os.path.dirname(__file__))
# # 获取当前窗口的路径, 当用 Linux 的时候切用这个,不然会报错。(windows也可以用这个)
# __C.COMMON.BASE_PATH = os.getcwd()__C.COMMON.DATA_PATH = os.path.join(__C.COMMON.BASE_PATH, "dataset")# 图像的形状
__C.COMMON.DATA_RESHAPE = [-1, 28, 28, 1]
# 图像 rezise 的形状
__C.COMMON.DATA_RESIZE = (32, 32)# 训练配置
__C.TRAIN = edict()# 学习率
__C.TRAIN.LEARNING_RATE = 0.01
# batch_size
__C.TRAIN.BATCH_SIZE = 32
# 迭代次数
__C.TRAIN.N_EPOCH = 10# 模型保存路径, 使用相对路径,方便移植
__C.TRAIN.MODEL_SAVE_PATH = "./checkpoint/model_"
# dropout 的持有量,0.7 表示持有 70% 的节点。
__C.TRAIN.KEEP_PROB_DROPOUT = 0.7# 测试配置
__C.TEST = edict()# 测试模型保存路径
__C.TEST.CKPT_MODEL_SAVE_PATH = "./checkpoint/model_acc=0.984000.ckpt-10"# 日志配置
__C.LOG = edict()
# 日志保存路径,后面会接上  train 或 test: 如 mnist_log_train
__C.LOG.LOG_SAVE_PATH = "./logs/mnist_log_"

(6). 公共代码 common.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/08 19:26
# @Author   : WanDaoYi
# @FileName : common.py
# ============================================import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from config import cfg
import numpy as npclass Common(object):def __init__(self):# 数据路径self.data_file_path = cfg.COMMON.DATA_PATHpass# 读取数据def read_data(self):# 数据下载地址: http://yann.lecun.com/exdb/mnist/mnist_data = input_data.read_data_sets(self.data_file_path, one_hot=True)train_image = mnist_data.train.imagestrain_label = mnist_data.train.labels_, n_feature = train_image.shape_, n_label = train_label.shapereturn mnist_data, n_feature, n_label# bn 操作def deal_bn(self, input_data, train_flag=True):bn_info = tf.layers.batch_normalization(input_data, beta_initializer=tf.zeros_initializer(),gamma_initializer=tf.ones_initializer(),moving_mean_initializer=tf.zeros_initializer(),moving_variance_initializer=tf.ones_initializer(),training=train_flag)return bn_infopass# 池化处理def deal_pool(self, input_data, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1),padding="VALID", name="avg_pool"):pool_info = tf.nn.avg_pool(value=input_data, ksize=ksize,strides=strides, padding=padding,name=name)tf.summary.histogram('pooling', pool_info)return pool_infopass# dropout 处理def deal_dropout(self, hidden_layer, keep_prob):with tf.name_scope("dropout"):tf.summary.scalar('dropout_keep_probability', keep_prob)dropped = tf.nn.dropout(hidden_layer, keep_prob)tf.summary.histogram('dropped', dropped)return droppedpass# 参数记录def variable_summaries(self, param):with tf.name_scope('summaries'):mean = tf.reduce_mean(param)tf.summary.scalar('mean', mean)with tf.name_scope('stddev'):stddev = tf.sqrt(tf.reduce_mean(tf.square(param - mean)))tf.summary.scalar('stddev', stddev)tf.summary.scalar('max', tf.reduce_max(param))tf.summary.scalar('min', tf.reduce_min(param))tf.summary.histogram('histogram', param)# 全连接操作def neural_layer(self, x, n_neuron, name="fc"):# 包含所有的计算节点对于这一层, name_scope 可写可不写with tf.name_scope(name=name):n_input = int(x.get_shape()[1])stddev = 2 / np.sqrt(n_input)# 这层里面的w可以看成是二维数组,每个神经元对于一组w参数# truncated normal distribution 比 regular normal distribution的值小# 不会出现任何大的权重值,确保慢慢的稳健的训练# 使用这种标准方差会让收敛快# w参数需要随机,不能为0,否则输出为0,最后调整都是一个幅度没意义with tf.name_scope("weights"):init_w = tf.truncated_normal((n_input, n_neuron), stddev=stddev)w = tf.Variable(init_w, name="weight")self.variable_summaries(w)with tf.name_scope("biases"):b = tf.Variable(tf.zeros([n_neuron]), name="bias")self.variable_summaries(b)with tf.name_scope("wx_plus_b"):z = tf.matmul(x, w) + btf.summary.histogram('pre_activations', z)return z# 卷积操作def conv2d(self, input_data, filter_shape, strides_shape=(1, 1, 1, 1),padding="VALID", train_flag=True, name="conv2d"):with tf.variable_scope(name):weight = tf.get_variable(name="weight", dtype=tf.float32,trainable=train_flag,shape=filter_shape,initializer=tf.random_normal_initializer(stddev=0.01))conv = tf.nn.conv2d(input=input_data, filter=weight,strides=strides_shape, padding=padding)conv_2_bn = self.deal_bn(conv, train_flag=train_flag)return conv_2_bnpasspass

(7). 模型代码 model_net.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/08 22:26
# @Author   : WanDaoYi
# @FileName : model_net.py
# ============================================import tensorflow as tf
from core.common import Commonclass ModelNet(object):def __init__(self):self.common = Common()passdef lenet_5(self, input_data, n_label=10, keep_prob=1.0, train_flag=True):with tf.variable_scope("lenet-5"):conv_1 = self.common.conv2d(input_data, (5, 5, 1, 6), name="conv_1")tanh_1 = tf.nn.tanh(conv_1, name="tanh_1")avg_pool_1 = self.common.deal_pool(tanh_1, name="avg_pool_1")conv_2 = self.common.conv2d(avg_pool_1, (5, 5, 6, 16), name="conv_2")tanh_2 = tf.nn.tanh(conv_2, name="tanh_2")avg_pool_2 = self.common.deal_pool(tanh_2, name="avg_pool_2")conv_3 = self.common.conv2d(avg_pool_2, (5, 5, 16, 120), name="conv_3")tanh_3 = tf.nn.tanh(conv_3, name="tanh_3")reshape_data = tf.reshape(tanh_3, [-1, 120])dropout_1 = self.common.deal_dropout(reshape_data, keep_prob)fc_1 = self.common.neural_layer(dropout_1, 84, name="fc_1")tanh_4 = tf.nn.tanh(fc_1, name="tanh_4")dropout_2 = self.common.deal_dropout(tanh_4, keep_prob)fc_2 = self.common.neural_layer(dropout_2, n_label, name="fc_2")scale_2 = self.common.deal_bn(fc_2, train_flag=train_flag)result_info = tf.nn.softmax(scale_2, name="result_info")return result_infopass

这里的模型,我使用了 lenet-5,当然,以后想换其他模型,也是可以的。在 lenet-5 里面,模型的输入 是要求 shape 要求是 32 x 32 大小的图片,不然,尺度不够的话,模型会报错的。所以,要将图像 resize 为 32 x 32 大小。

(8). 训练代码 cnn_mnist_train.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/08 19:24
# @Author   : WanDaoYi
# @FileName : cnn_mnist_train.py
# ============================================from datetime import datetime
import tensorflow as tf
from config import cfg
from core.common import Common
from core.model_net import ModelNetclass CnnMnistTrain(object):def __init__(self):# 模型保存路径self.model_save_path = cfg.TRAIN.MODEL_SAVE_PATHself.log_path = cfg.LOG.LOG_SAVE_PATHself.learning_rate = cfg.TRAIN.LEARNING_RATEself.batch_size = cfg.TRAIN.BATCH_SIZEself.n_epoch = cfg.TRAIN.N_EPOCHself.data_shape = cfg.COMMON.DATA_RESHAPEself.data_resize = cfg.COMMON.DATA_RESIZEself.common = Common()self.model_net = ModelNet()# 读取数据和 维度self.mnist_data, self.n_feature, self.n_label = self.common.read_data()# 创建设计图with tf.name_scope(name="input_data"):self.x = tf.placeholder(dtype=tf.float32, shape=(None, self.n_feature), name="input_data")self.y = tf.placeholder(dtype=tf.float32, shape=(None, self.n_label), name="input_labels")with tf.name_scope(name="input_shape"):# 784维度变形为图片保持到节点# -1 代表进来的图片的数量、28,28是图片的高和宽,1是图片的颜色通道image_shaped_input = tf.reshape(self.x, self.data_shape)# 将 输入 图像 resize 成 网络所需要的大小image_resize = tf.image.resize_images(image_shaped_input, self.data_resize)tf.summary.image('input', image_resize, self.n_label)self.keep_prob_dropout = cfg.TRAIN.KEEP_PROB_DROPOUTself.keep_prob = tf.placeholder(tf.float32)# 获取最后一层 lenet_5 的返回结果self.result_info = self.model_net.lenet_5(image_resize, n_label=self.n_label,keep_prob=self.keep_prob_dropout)# 计算损失with tf.name_scope(name="train_loss"):# 定义损失函数self.cross_entropy = tf.reduce_mean(-tf.reduce_sum(self.y * tf.log(self.result_info),reduction_indices=[1]))tf.summary.scalar("train_loss", self.cross_entropy)passwith tf.name_scope(name="optimizer"):self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate)self.train_op = self.optimizer.minimize(self.cross_entropy)passwith tf.name_scope(name="accuracy"):self.correct_pred = tf.equal(tf.argmax(self.result_info, 1), tf.argmax(self.y, 1))self.acc = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32))tf.summary.scalar("accuracy", self.acc)pass# 因为我们之前定义了太多的tf.summary汇总操作,逐一执行这些操作太麻烦,# 使用tf.summary.merge_all()直接获取所有汇总操作,以便后面执行self.merged = tf.summary.merge_all()self.sess = tf.InteractiveSession()# 保存训练模型self.saver = tf.train.Saver()# 定义两个tf.summary.FileWriter文件记录器再不同的子目录,分别用来存储训练和测试的日志数据# 同时,将Session计算图sess.graph加入训练过程,这样再TensorBoard的GRAPHS窗口中就能展示self.train_writer = tf.summary.FileWriter(self.log_path + 'train', self.sess.graph)self.test_writer = tf.summary.FileWriter(self.log_path + 'test')pass# 灌入数据def feed_dict(self, train_flag=True):# 训练样本if train_flag:# 获取下一批次样本x_data, y_data = self.mnist_data.train.next_batch(self.batch_size)keep_prob = self.keep_prob_dropoutpass# 验证样本else:x_data, y_data = self.mnist_data.test.images, self.mnist_data.test.labelskeep_prob = 1.0passreturn {self.x: x_data, self.y: y_data, self.keep_prob: keep_prob}passdef do_train(self):# 定义初始化init = tf.global_variables_initializer()self.sess.run(init)test_acc = Nonefor epoch in range(self.n_epoch):# 获取总样本数量batch_number = self.mnist_data.train.num_examples# 获取总样本一共几个批次size_number = int(batch_number / self.batch_size)for number in range(size_number):summary, _ = self.sess.run([self.merged, self.train_op], feed_dict=self.feed_dict())# 第几次循环i = epoch * size_number + number + 1self.train_writer.add_summary(summary, i)if number == size_number - 1:# 获取下一批次样本x_batch, y_batch = self.mnist_data.train.next_batch(self.batch_size)acc_train = self.acc.eval(feed_dict={self.x: x_batch, self.y: y_batch})print("acc_train: {}".format(acc_train))# 验证 方法二 两个方法,随便挑一个都可以的。test_summary, acc_test = self.sess.run([self.merged, self.acc], feed_dict=self.feed_dict(False))print("epoch: {}, acc_test: {}".format(epoch + 1, acc_test))self.test_writer.add_summary(test_summary, epoch + 1)test_acc = acc_testpasssave_path = self.model_save_path + "acc={:.6f}".format(test_acc) + ".ckpt"# 保存模型self.saver.save(self.sess, save_path, global_step=self.n_epoch)self.train_writer.close()self.test_writer.close()passif __name__ == "__main__":# 代码开始时间start_time = datetime.now()print("开始时间: {}".format(start_time))demo = CnnMnistTrain()demo.do_train()# 代码结束时间end_time = datetime.now()print("结束时间: {}, 训练模型耗时: {}".format(end_time, end_time - start_time))

训练代码,只是粗来训练一下,没做到 网格搜索,也没做到 fine-tunning。更没有 early-stopping,有兴趣的,可以自己添加一下。

(9). 测试代码 cnn_mnist_test.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/08 19:24
# @Author   : WanDaoYi
# @FileName : cnn_mnist_test.py
# ============================================from datetime import datetime
import tensorflow as tf
import numpy as np
from config import cfg
from core.common import Common
from core.model_net import ModelNetclass CnnMnistTest(object):def __init__(self):self.common = Common()self.model_net = ModelNet()# 读取数据和 维度self.mnist_data, self.n_feature, self.n_label = self.common.read_data()# ckpt 模型self.test_ckpt_model = cfg.TEST.CKPT_MODEL_SAVE_PATHprint("test_ckpt_model: {}".format(self.test_ckpt_model))# tf.reset_default_graph()# 创建设计图with tf.name_scope(name="input"):self.x = tf.placeholder(dtype=tf.float32, shape=(None, self.n_feature), name="input_data")self.y = tf.placeholder(dtype=tf.float32, shape=(None, self.n_label), name="input_labels")self.data_shape = cfg.COMMON.DATA_RESHAPEself.data_resize = cfg.COMMON.DATA_RESIZEwith tf.name_scope(name="input_shape"):# 784维度变形为图片保持到节点# -1 代表进来的图片的数量、28 x 28 是图片的高和宽,1是图片的颜色通道self.image_shaped_input = tf.reshape(self.x, self.data_shape)# 将 输入 图像 resize 成 网络所需要的大小 32 x 32self.image_resize = tf.image.resize_images(self.image_shaped_input, self.data_resize)# 获取最后一层 lenet_5 的返回结果self.result_info = self.model_net.lenet_5(self.image_resize, n_label=self.n_label)pass# 预测def do_ckpt_test(self):saver = tf.train.Saver()with tf.Session() as sess:saver.restore(sess, self.test_ckpt_model)# 预测output = self.result_info.eval(feed_dict={self.x: self.mnist_data.test.images})# 将 one-hot 预测值转为 数字y_perd = np.argmax(output, axis=1)print("预测值: {}".format(y_perd[: 5]))# 真实值y_true = np.argmax(self.mnist_data.test.labels, axis=1)print("真实值: {}".format(y_true[: 5]))passpassif __name__ == "__main__":# 代码开始时间start_time = datetime.now()print("开始时间: {}".format(start_time))demo = CnnMnistTest()# 使用 ckpt 模型测试demo.do_ckpt_test()# 代码结束时间end_time = datetime.now()print("结束时间: {}, 训练模型耗时: {}".format(end_time, end_time - start_time))

(10). 日志效果查看

acc 的图像:

graphs 图像:

在 tensorboard 日志里面,可以双击 lenet-5,模型结构如下:

打开日志 graphs 之后,可以放大来看清晰的图像

从这 DNN 和 CNN 的训练中,不难看成,对于图像信息预测,CNN 比 DNN 要好上一些(DNN 的 10 个 epoch 才 96% 的精度,而 CNN 的 10 个 epoch 精度则达到了 98%,当然,这个只是前期的训练效果,不好直接说明什么。但是,两者训练的次数足够的话,还是 CNN 的效果会稍微好点的。这,就是为什么,在图像处理中,大多使用 CNN,而不是纯粹的 DNN)

返回主目录

返回 CNN 卷积神经网络目录

上一章:深度篇—— CNN 卷积神经网络(三) 关于 ROI pooling 和 ROI Align 与 插值

深度篇—— CNN 卷积神经网络(四) 使用 tf cnn 进行 mnist 手写数字 代码演示项目相关推荐

  1. TensorFlow高阶 API: keras教程-使用tf.keras搭建mnist手写数字识别网络

    TensorFlow高阶 API:keras教程-使用tf.keras搭建mnist手写数字识别网络 目录 TensorFlow高阶 API:keras教程-使用tf.keras搭建mnist手写数字 ...

  2. 使用tf.keras搭建mnist手写数字识别网络

    使用tf.keras搭建mnist手写数字识别网络 目录 使用tf.keras搭建mnist手写数字识别网络 1.使用tf.keras.Sequential搭建序列模型 1.1 tf.keras.Se ...

  3. 《深度学习之TensorFlow》reading notes(3)—— MNIST手写数字识别之二

    文章目录 模型保存 模型读取 测试模型 搭建测试模型 使用模型 模型可视化 本文是在上一篇文章 <深度学习之TensorFlow>reading notes(2)-- MNIST手写数字识 ...

  4. 深度学习21天——卷积神经网络(CNN):实现mnist手写数字识别(第1天)

    目录 一.前期准备 1.1 环境配置 1.2 CPU和GPU 1.2.1 CPU 1.2.2 GPU 1.2.3 CPU和GPU的区别 第一步:设置GPU 1.3 MNIST 手写数字数据集 第二步: ...

  5. 深度学习之卷积神经网络(Convolutional Neural Networks, CNN)(二)

    前面我们说了CNN的一般层次结构, 每个层的作用及其参数的优缺点等内容.深度学习之卷积神经网络(Convolutional Neural Networks, CNN)_fenglepeng的博客-CS ...

  6. 深度学习100例-卷积神经网络(CNN)实现mnist手写数字识别 | 第1天

    文章目录 一.前期工作 1. 设置GPU(如果使用的是CPU可以忽略这步) 2. 导入数据 3. 归一化 4. 可视化图片 5. 调整图片格式 二.构建CNN网络模型 三.编译模型 四.训练模型 五. ...

  7. 【FPGA教程案例100】深度学习1——基于CNN卷积神经网络的手写数字识别纯Verilog实现,使用mnist手写数字数据库

    FPGA教程目录 MATLAB教程目录 ---------------------------------------- 目录 1.软件版本 2.CNN卷积神经网络的原理 2.1 mnist手写数字数 ...

  8. TensorFlow 2.0 mnist手写数字识别(CNN卷积神经网络)

    TensorFlow 2.0 (五) - mnist手写数字识别(CNN卷积神经网络) 源代码/数据集已上传到 Github - tensorflow-tutorial-samples 大白话讲解卷积 ...

  9. DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Functional)利用MNIST(手写数字图片识别)数据集实现多分类预测

    DL之CNN:利用卷积神经网络算法(2→2,基于Keras的API-Functional)利用MNIST(手写数字图片识别)数据集实现多分类预测 目录 输出结果 设计思路 核心代码 输出结果 下边两张 ...

最新文章

  1. 硕博研究生期间应该明确的50件事
  2. 2-Entity RANSAC:在变化的环境中进行稳健的视觉定位
  3. 老大吩咐的可重入分布式锁,终于完美的实现了~
  4. struts2的文件上传和文件下载
  5. java正则表达式性能_译:Java 中的正则表达式性能概述
  6. 六、解释红外线纺织品的保健、保暖作用?
  7. nginx 配置文件的匹配规则
  8. 2021年终总结:30多岁依然没有放弃自我成长!
  9. 圣诞节生成头像微信小程序源码1.3.0
  10. A 洛谷 P3601 签到题 [欧拉函数 质因子分解]
  11. SQL server 2017安装教程
  12. 浙大 PAT 甲级 1077 Kuchiguse
  13. String对象的match方法
  14. LinuxStudyNote(6)-Linux常用命令(2)-文件处理命令(1)-目录处理命令ls、所有者所属组其他人、文件权限、i节点
  15. 计算机主板 也叫系统板或母版,电脑主板与CPU常见故障维修
  16. 银盒子智慧餐厅硬件尺寸规格推荐机型
  17. H5案例分享:微信视频播放全屏问题
  18. 高温大量程热流传感器结构及信号调理电路的设计
  19. 【视频分享】尚硅谷Java视频教程_Jenkins视频教程
  20. 东区机房浏览器问题记录排查

热门文章

  1. 简述如何编辑出一篇漂亮的微信公众号文章
  2. 小三大战又一季:小米舞剑,意在雷电OS
  3. Machine Learning Stanford (week 1)
  4. 用心去写的neo4j教程01-概述
  5. 怎样一键生成微信跳转链接,可以自动跳转浏览器打开URL网页
  6. 区块链技术在商品溯源上的应用场景
  7. 什么是匿名对象,匿名对象怎么使用
  8. VC++ 利用Opencv 做的一个发票识别程序,识别有误
  9. 各种软件的常用快捷键(for mac 持续更新)
  10. python生成微信好友头像心形照片墙