-
2021-05-22 15:01:48
1 .研究背景
手手写数字识这项技术是光学字符识别(Optical Character Recognition,简称OCR)的一个重要分支,主要分为脱机手写数字识别和联机手写数字识别。其中,联机手写数字识别相对较简 单些,它利用实时监控数字输入终端将所写数字笔划变为一维电信号、笔尖移动的轨迹变为 坐标点序列输入电脑,因此联机手写数字识别处理的是一维笔划串信息,这些信息涵盖了笔划走向、笔划顺序、书写速度和笔划总数,具有一定的连续性和追溯性。而脱机手写数字识别则不同,其主要是由终端输入静止的、不可追溯的二维数字点阵图像,计算机处理起来对复杂。本文研究的是脱机手写数字识别。
手写数字识别的基本原理是将输入样本数字与对应的标准样本数字进行模式匹配,将具有最大类似度的样本数字作为识别结果。在整个识别过程中,关键的是对样本特征提取方法的选择和分类器的设计,快速而全面的特征提取方法和高效而准确的分类器设计,决定着识别系统的分类效果和性能优劣。
由于手写数字识别没有上下文,不存在语义相关性,而一些部门对数字的要求又相对较高,如银行报表、邮政编码、财务报表等,所以对其识别精度也需要达到更高的水平。因为数字识别有广阔 的应用前景和实用价值,并且在文献检索、办公自动化、邮政自动分拣、银行票据自动识别 等领域也有重要应用,因此国内外许多研究学者也在为进一步提高识别率和通用性而努力. 并且,现今越来越多的智能信息处理系统和终端配备了电子摄像或扫描设备,数字识别可广 泛地应用于这类设备的输入、电子商务系统的签名鉴别等领域中,为文字信息处理的自动化 及进一步提升计算机智能输入提供重要的理论意义及广阔的应用前景,因此手写数字识别有着重大的现实意义。
2.国内外研究现状
手写数字识别作为人工智能的一个重要组成部分,有着极其广泛地应用前景和发展。
首先就国外来讲,阿拉伯数字作为全球通用的数字语言,与各个国家、民族和地区的文化背景无关,是世界上统一使用的符号。数字的识别类别较小,可以方便评估研究方法的有效性和可行性,同时也为字符的识别提供借鉴。最后,数字识别的智能系统,可以应用于财税、金融、邮件分拣等领 域,减少了人工操作带来的不便性和出错率,方便人们生活,为人类向智能领域的发展提供 更大的实用价值。
目前应用于实际生活中为人么所熟知,比如击败李世石的ALPHA GO以及后来青出于蓝而胜于蓝的ALPHA GO ZERO[1] 以及网络春晚大放异彩的钢琴机器人等。 数字识别的方法有MRFSE、运动图像(2)、SVM(3)、BP[4]、聚类分析问等,这些方法在识别之前,需要对样本图像进行统一的预处理,如归一化、平滑去噪、字符 切分、二值化、笔画细化、特征提取、重建模型等,再加上网络训练参数众多使得识别过程 更加耗时、复杂。在实验过程中,大量实践表明,没有一种方法可以获得各种字符特征的100% 识别率,每个研究方法都存在局限性。
卷积神经网络的网络结构使其更接近于生物神经系统,从而可以快速学习并良好记忆。
由牛津大学提出的识别、人脸检测、物体形状等的识别,成为语音分析和图像识别领域的研究热点,并取得 —定研究成果,具有良好的应用前景。
3.研究的目的和意义
手写数字识别目前已经形成了比较成熟的处理流程,一般是扫描仪扫描原始手写数字转 换成二值图像并进行预处理,然后对数字进行特征提取。接下来是将提取到的特征作为识别 阶段的依据,在识别阶段,将提取的特征输入分类器模型,最后由模型计算得出识别结果。
更多相关内容 -
3D直写技术的研究进展与应用前景
2020-03-10 18:31:143D直写技术的研究进展与应用前景,陈燎,唐兴伟,3D直写(three dimensional direct-writing)技术是3D打印技术的一个分支,其打印精度高、打印方式灵活、打印材料多种多样,在诸多领域取得了 -
常用编程语言应用、前景及学习方法
2022-04-29 00:27:24常用编程语言应用、前景及学习方法 常用编程语言应用和前景 C语言 很多高级语言的鼻祖,个人觉得最大的缺点是:他是一门面向过程的语言。主要用于嵌入式等底层领域 C++ 带面向对象的C语言,运行速度快,有人曾说:...常用编程语言应用、前景及学习方法
常用编程语言应用和前景
C语言
很多高级语言的鼻祖,个人觉得最大的缺点是:他是一门面向过程的语言。主要用于嵌入式等底层领域
C++
带面向对象的C语言,运行速度快,有人曾说:Python什么都可以干,其实C++也不例外,而且运行效率比Python高很多,但是吧,C++的入门门槛不是一般的高呀。
Java
Java是在C++的基础上演变而来,可以认为Java是轻量款的C++,但是Java舍弃了C++里面很多复杂的概念,比如:指针
主要应用移动开发,可移植性强,也可做后端开发,主要框架spring。虽然前(钱)景非常不错,但是学的人非常多,导致这门语言很卷。
Python
由于Python优雅、简单、易上手且几乎涵盖计算机的全部领域,使其成为目前很火的一门语言。大家普遍认为Python的运行速度很慢,但是这几年Python的发展,他的运行速度已经没有那么慢了,像YouTube,日访问量高达20亿,就是用Python的框架Django写的,像深度学习,数据分析等如此高的计算量,依旧用Python实现,主要原因使因为Python的底层是用C++写的,而且Python也可以调用C++的接口。
Go
Go语言,又叫Golang,是谷歌公司开发的一门语言,可以将他看成是C语言和Python的结合版,生态比较完整,主要用于区块链和Web服务器开发,主流框架Beego,Echo,Gin等等,B站目前就在用Go语言重构后端。
C#
微软的亲儿子,起初微软开发该语言的目的是为了干掉Java,但是,最后…,所以C#代码和Java代码结构很相似。毕竟是微软的亲儿子,所以生态链还是比较完整的,主要用于Windows平台下的开发。前几年确实有点掉队,但是近几年在慢慢爬坡。
JavaScript
目前主流编译器上唯一支持的脚本语言,与HTML,CSS并称为“前端三剑客”,是学习前端必学的语言,该语言是由Netscape公司的Brendan Eich用了不到两周的时间开发出来的,所以该语言的一些语法很随意,导致很难调试。该语言创立之初与微软的JScript和CEnvi的ScriptEase三足鼎立,最后JavaSprice成功的一统天下(微软的开发的语言又被干掉了/)。
PHP
一门简单的web开发语言,曾经被称为是最好的语言,但是近几年可以说是江郎才尽了。
其实语言都是相通的,当你学完一门语言后,再去学其他语言,就会很轻松
在你掌握一门语言后,再去学其他语言时,要注意每个语言特点,如:变量是怎么定义的,循环是怎么写的。其次还要注意每个语言独有的,如:C/C++的指针这一概念就是其他语言所没有的
学习方法
学习编程就是两个阶段,输入和输出。
输入
输入阶段要做的就是拼命地吸收知识,构建完整的知识体系。
可以分三步走
第一步就是先看视频,看b站,慕课等网站的免费视频就可以,当然也可以去国外的一些网站,如Youtube上也有免费的视频,不得不说有些国外的视频比国内的视频好太多了。开视频时至少开1.5以上倍速度,这个过程不是为了让你学会的,而是因为视频教学可以帮你快速地提炼知识、缕清知识的脉络。此时最好把重点都给写下来,也可以跟着视频完成相应的内容。
第二步就是去搜技术博客,看开源项目,博客要是相同的知识点,但是讲得不太一样的博客。这样你会得到补充,慢慢了解这个知识本来的样子,因为很多教学视频都是阉割版的,所以我们要看看别人是怎么说的。同时也可以去Github上找一些相应的开源项目,GitHub作为全球最大的开源网站,肯定能找到合适开源项目,主要看实现这些项目的方法,最好是自己能将开源项目跑一遍。
第三步看专业书籍,看官网的文档,一般能出书都是体系化,你会发现视频里讲的,博客里写的其实早就在梳理总结过。一定要在最后看书,一上来就看书很容易就被劝退了,因为说这个东西是需要细嚼慢咽的。尤其那些被称为该领域里“圣经”的书,刚开始学千万别看,既然都被称为“圣经”,那是一般人能看懂的吗?没有一定的功底,千万别去看这些书。其次就是看官方文档,官方文档里的内容一般都是是最新、最权威、最专业的,但是有很多官方文档都是英文的,这个不要怕,其实主要看懂一些关键字,这句话就理解了,他不想我们平时做的英语阅读理解那么晦涩难懂,再不济还有谷歌翻译。而且看文档,尤其是英文文档,是进阶高阶程序员必备的一项技能。
输出
输出阶段要做的就是把你吸收的知识给吐出来,你能吐出来也就会用了,这个才是真正学会了。
推荐4种比较好用的方式
第一种就是画思维导图,它可以帮你理清知识脉络。
第二种就是写技术博客,和项目,这个一定要坚持写,因为搞技术的一定要激励自己,这不仅有助于你的学习工作,对以后的面试也是相当有用的。自己写的项目,可以发布在github上,这样有利于自己提高,也有利于他人学习。一个程序员没有几万行代码根本就出不来。
第三种就是利用好碎片时间去做复习,比如在睡觉前、地铁上,甚至在上厕所的时候就把所学的知识都在脑子过一遍,这一招真的特别用。
第四种就是尝试把你学的讲给别人听,你但凡能给别人讲懂了啊,这个知识你一定是理解的非常深刻的。写在最后:
1.没有最好用的语言,只有不会用语言的程序员
2.计算机行业是技术更新非常快,只有不断学习才能不被淘汰
-
联邦学习分类及前景应用
2021-01-26 16:26:04联合学习的前景与应用4. 模型鲁棒性4.1 恶意攻击4.2 非恶意故障参考文献 联邦学习 第一次开始写博客,就从前两天调研的联邦学习开始好了。 下面将以联邦学习的分类、联邦学习与分布式学习的区别、联邦学习的前景与...联邦学习
第一次开始写博客,就从前两天调研的联邦学习开始好了。
下面将以联邦学习的分类、联邦学习与分布式学习的区别、联邦学习的前景与应用以及模型鲁棒性四个方面展开。
联邦学习的分类
联邦学习的孤岛数据有不同的分布特征。对于每一个参与方来说,自己所拥有的数据可以用一个矩阵来表示。设矩阵Di表示第i个参与方的数据;设矩阵Di的每一行表示一个数据样本,每一列表示一个具体的数据特征(feature)。同时,一些数据集还可能包含标签信息。我们将特征空间设为X,数据标签(label)空间设为Y,并用I表示数据样本ID空间。
特征空间X、数据标签空间Y和样本ID空间I组成了一个训练数据集(I,X,Y)。不同的参与方拥有的数据的特征空间和样本ID空间可能都是不同的。例如,在金融领域,数据标签可以是用户的信用度或者征信信息;在市场营销领域,数据标签可以是用户的购买计划;在教育领域,数据标签可以是学生的成绩分数。
1. 根据数据特点分类
根据训练数据在不同参与方之间的数据特征空间和样本ID空间的分布情况,联邦学习可被分为横向联邦学习(Horizontal Federated Learning,HFL)、纵向联邦学习(Federated Transfer Learning,FTL) 、迁移联邦学习(Vertical Federated Learning,VFL)。
横向联邦学习适用于联邦学习的参与方的数据有重叠的数据特征,即数据特征在参与方之间是对齐的,但是参与方拥有的数据样本是不同的。横向联邦学习中数据特征重叠维度较多,根据重合维度进行对齐,取出参与方数据中特征相同而用户不完全相同的部分进行联合训练。
图 横向联邦学习(按样本划分的联邦学习)纵向联邦学习适用于联邦学习参与方的训练数据有重叠的数据样本,即参与方之间的数据样本是对齐的,但是在数据特征上有所不同。纵向联邦学习用户重合较多,根据用户ID进行匹配,取出参与方数据中用户相同而特征不完全相同的部分进行联合训练。
图 纵向联邦学习(按特征划分的联邦学习)联邦迁移学习适用于参与方的数据样本和数据特征重叠都很少的情况。目前大部分的研究是基于横向联邦学习和纵向联邦学习的,迁移联邦学习领域的研究暂时还不多。
图 联邦迁移学习2. 根据场景分类
联邦学习根据不同场景可以分为两大类:“跨设备”(cross-device)和“跨孤岛”(cross-silo)。表 1所示是两个类型的主要区别。
“跨设备”类型着重于整合大量移动端和边缘设备应用程序。跨设备的例子:Google的Gboard移动键盘,Apple正在iOS 13中使用跨设备FL用于QuickType键盘和“ Hey Siri”的人声分类器等。
“跨孤岛”类型一些可能只涉及少量相对可靠的客户端的应用程序,例如多个组织合作训练一个模型。跨孤岛的例子包括再保险的财务风险预测,药物发现,电子健康记录挖掘,医疗数据细分和智能制造等。
跨孤岛 跨设备 例子 医疗机构 手机端应用 节点数量 1~100 1~1010 节点状态 节点几乎稳定运行 大部分节点不在线 主要瓶颈 计算瓶颈和通信瓶颈 wifi速度,设备不在线 按数据类型分类 横向/纵向 横向 联邦学习与分布式学习的区别
联邦学习并不只是使用分布式的方式解决优化问题。联邦学习和分布式学习均是在多个计算节点上进行模型运算,但仍有不少区别,其主要区别如下表所示。
联邦学习 分布式训练 数据分布 分散存储且固定,数据无法互通、可能存在数据的Non-IID(非独立同分布) 集中存储不固定,可以任意打乱、平衡地分配给所有客户端 节点数量 1~1010 1~100 节点状态 节点可能不在线 所有节点稳定运行 联邦学习是面向隐私保护的ML的框架,原始数据分散保存在各个设备上并进行训练,节点间数量较多且质量严重不均。服务器聚合各个本地计算的模型更新。
分布式学习则利用多个计算节点进行机器学习或者深度学习的算法和系统,其旨在提高性能,并可扩展至更大规模的训练数据和更大的模型。各节点间数据共享,任务由服务器统一分配,各节点比较均衡。
表 数据集中式分布式学习与跨孤岛/跨设备联邦学习的综合对比
数据集中式的分布式学习 跨孤岛的联邦学习 跨设备的联邦学习 设置 在大型但“扁平”的数据集上训练模型。客户端是单个群集或数据中心中的计算节点。 在数据孤岛上训练模型。客户是不同的组织(例如,医疗或金融)或地理分布的数据中心。 客户端是大量的移动或物联网设备 数据分布 数据被集中存储,可以在客户端之间进行混洗和平衡。任何客户端都可以读取数据集的任何部分。 数据在本地生成,并保持分散化。每个客户端都存储自己的数据,无法读取其他客户端的数据。数据不是独立或相同分布的。 与跨孤岛的数据分布一样 编排方式 中央式编排 中央编排服务器/服务负责组织培训,但从未看到原始数据。 与跨数据孤岛编排方式一样 广域通讯 无(在一个数据中心/群集中完全连接客户端)。 中心辐射型拓扑,中心代表协调服务提供商(通常不包含数据),分支连接到客户端。 与跨孤岛的广域通讯方式一样 数据可用性 所有客户端都是可用的 所有客户端都是可用的 在任何时候,只有一小部分客户可用,通常会有日间或其他变化。 数据分布范围 通常1-1000个客户端 通常2~1000个客户端 大规模并行,最多10^10个客户端。 主要瓶颈 在可以假设网络非常快的情况下,计算通常是数据中心的瓶颈。 可能是计算和通信量 通信通常是主要的瓶颈,尽管这取决于任务。通常跨设备联邦学习使用wifi或更慢的连接。 可解决性 每个客户端都有一个标识或名称,该标识或名称允许系统专门访问它。 与数据集中式的分布式学习一样 无法直接为客户建立索引(即不对用户进行标记)。 客户状态 有状态的-每个客户都可以参与到计算的每一轮中,不断地传递状态。 有状态的-每个客户都可以参与到计算的每一轮中,不断地传递状态。 高度不可靠-预计有5%或更多的客户端参与一轮计算会失败或退出(例如,由于违反了电池,网络或闲置的要求而导致设备无法使用)。 客户可靠性 相对较少的失败次数 相对较少的失败次数。 无状态的-每个客户在一个任务中可能只参与一次,因此通常假定在每轮计算中都有一个从未见过的客户的新样本。 数据分区轴 数据可以在客户端之间任意分区/重新分区。 固定分区。能够根据样本分区(横向)或者特征分区(纵向)。 根据样本固定分区(横向)。 3. 联合学习的前景与应用
如下表所示,联合学习应用领域广泛。谷歌的研究人员致力于在Gboard应用程序上从用户生成的数据增强语言建模。其他人发现联合学习非常适合医疗保健领域,可以通过在医院保留患者数据来平衡患者隐私和机器学习。物联网设备也在联合学习上获得了关注。
此外,联合学习也进入了许多其他领域,如边缘计算、网络 、机器人 、网格、联合学习增强、推荐系统 、网络安全、在线零售商、无线通信和电动汽车。
表 联邦学习应用领域
领域 训练设备类型 目标 训练模型 聚合算法 Google Gboard 应用 移动电话 语言建模:键盘搜索建议 Logistic Regression FedAvg Google Gboard 应用 移动电话 语言建模:下一个单词预测 RNN FedAvg Google Gboard 应用 移动电话 语言建模:表情符号预测 RNN-LSTM FedAvg Google Gboard 应用 移动电话 语言建模:词汇外的学习 RNN-LSTM FedAvg 医疗健康 医院 死亡率的预测 神经网络 提出的FADE 医疗健康 医院 死亡率和住院时间预测 深度学习 FedAvg 医疗健康 医院、患者 住院治疗的预测 Sparse SVM 提出的CPDS 医疗健康 连接到患者设备上的手机 医疗系统异常检测 神经网络 非加权的参数平均 医疗健康 组织 人类活动识别 深度神经网络 n/a 医疗健康 中心 神经系统疾病患者的脑变化分析 特征提取 交替方向乘子法 医疗健康 机构 脑瘤 CNN: U-Net FedAvg 医疗健康 机构 影像分类 深度神经网络 FedAvg 医疗健康 脑电图(EEG)设备 脑电图信号分类 CNN FedAvg 物联网系统 网关监控物联网设备 异常检测 RNN-GRU FedAvg 物联网系统 物联网对象或协调器(云服务器-边缘设备) 资源受限设备的轻量级学习 深度神经网络 n/a 物联网系统 物联网设备 Computation Offloading 双深度Q学习 n/a 物联网系统 移动电话和移动边缘计算服务器 提升物联网制造商服务 分块深度模型训练 n/a 边缘计算 用户设备、边缘节点 Computation Offloading 边缘兑现 强化学习 n/a 网络 机械类型设备(MTD) 资源块分配和电力传输 马尔科夫链 MTDs流量模型的聚合 网络 代理 生成Q网络政策 Q网络 多层感知器 机器人学 机器人 机器人导航决策 强化学习 提出的一种知识融合算法 联合学习增强 边缘节点 聚合频率的确定 基于梯度下降的ML模型 FedAvg 推荐系统 任何用户设备(包括笔记本电脑、手机) 生成个性化推荐 协同过滤 梯度聚合以更新因素向量 网络安全 网关监控的桌面节点 异常检测 自动编码 FedAvg 在线零售 客户 点击流的预测 RNN-RGU FedAvg 无线通信 增强现实使用者 边缘兑现 自动编码 n/a 无线通信 无线电设备 频谱管理 频谱使用率模型 n/a 无线通信 核心网络中的实体 5G核心网络 n/a n/a 电动汽车 车辆 电动汽车的故障预测 RNN-LSTM 基于损失函数的加权平均 4. 模型鲁棒性
Federated Learning系统可能遭受各种故障。这些故障不仅包括非恶意故障,还包括针对训练和部署管道的显式攻击。Federated Learning的分布式性质、体系结构设计和数据约束打开了新的故障模式和攻击面。
此外,在Federated Learning中保护隐私的安全机制可以使检测和纠正这些故障和攻击成为一项特别具有挑战性的任务。接下来关于Federated Learning鲁棒性的探讨将针对恶意攻击和非恶意故障展开。
4.1 恶意攻击
由于Federated Learning模型是通过(可能是大型的)大量不可靠的设备使用私有的、不可检查的数据集来训练模型的,因此Federated Learning可能会在训练时引入新的攻击面。恶意攻击一般有两种类型的攻击方式:数据中毒和模型更新中毒,模型如何规避攻击也同样需要探讨。这些攻击大致可分为训练时攻击(中毒攻击)和推理时攻击(逃避攻击)。
数据中毒
在数据污染攻击中,攻击者通过替换标签或数据的特定功能来操纵客户数据,而非直接破坏向中央节点中的联合模型。
通过用错误分类的标签替换正确标签或者注入了有毒数据的方式来攻击操纵训练阶段,导致模型本身行为不当。标签翻转是脏标签攻击的一种特殊情况,被证明是Federated Learning中的漏洞之一,使用差分隐私技术可以减轻这种攻击。
数据清理和网络修剪是专为数据中毒攻击而设计的防御措施。
- 数据清理旨在删除中毒或其他异常数据,最近的工作开发了使用健壮统计数据的改进的数据消毒方法,其对具有对少量异常值具有健壮性的优势。
- 而修剪防御尝试删除对干净数据无效的激活单元 。
模型更新中毒
模型更新中毒攻击不是向训练集中注入恶意数据,而是通过欺骗本地模型直接破坏全局模型。在联合设置中,可以通过直接破坏客户端的更新或某种中间人攻击来执行此操作。与数据中毒攻击相比,模型中毒攻击看起来不那么自然,但却更有效。如果入侵者之间串通,则模型更新中毒攻击的有效性可能会大大提高,这种勾结可以使对手创建更有效且更难检测的模型更新攻击。
防御中毒攻击
就目前而言,少有先进方法能保护系统免受中毒攻击。区块链技术和数据完整性被提出,以获得更健壮的Federated Learning解决方案。为了保证数据的私密性和安全性,在Federated Learning设置下,采用区块链模式建立了高效的数据访问控制,保证了大规模分布式数据的安全协作计算。考虑这样一种场景:一个客户端需要解决问题,一些客户端拥有适当的数据,而另一些客户端拥有具有足够计算资源的设备。对于这种场景,作者提出了一种加密方案,其中初始客户机创建公钥和私钥并加密模型参数。然后,适当的客户机协作利用所提供的资源和私有加密数据,以便成功地训练模型。
4.2 非恶意故障
与数据中心训练相比,Federated Learning特别容易受到服务提供商无法控制的不可靠客户的非恶意故障的影响。与恶意攻击一样,系统因素和数据限制也加剧了数据中心设置中出现的非恶意故障。
接下来我们将讨论三种可能的非恶意故障模式:客户端报告故障,数据管道故障和嘈杂的模型更新。
客户报告失败
在Federated Learning中,每轮培训都涉及向客户端广播模型,本地客户端计算以及向中央聚合器的客户端报告。客户报告失败在跨设备Federated Learning中尤其容易发生,在这种情况下,网络带宽变得更加受约束,并且客户端设备更有可能是计算能力有限的边缘设备。不幸的是,当使用安全聚合(SecAgg)时,无响应的客户端变得更具挑战性,尤其是如果客户端在SecAgg协议期间退出时。尽管SecAgg被设计为对大量丢失具有鲁棒性,但仍有失败的可能。此时可能需要提高SecAgg的效率或开发一种SecAgg的异步版本来改善。
数据管道故障
尽管Federated Learning中的数据管道仅存在于每个客户端中,但管道仍面临许多潜在问题。特别是,任何Federated Learning系统仍必须定义如何访问原始用户数据并将其预处理为训练数据,该管道中的错误或意外动作会极大地改变联合学习过程。尽管通常可以通过数据中心设置中的标准数据分析工具来发现数据管道错误,但Federated Learning中的数据限制使检测变得更加困难。例如,服务器无法直接检测到功能级别的预处理问题(例如,像素倒置,连接词等)。一种可能的解决方案是使用具有差分隐私的联合方法训练生成模型,然后使用它们来合成可用于调试基础数据管道的新数据样本。开发不直接检查原始数据的通用机器学习调试方法仍然是一个挑战。
嘈杂的模型更新
除了恶意攻击之外,网络和体系结构因素,也会使得发送到服务器的模型更新变得失真。即使客户端上的数据不是故意恶意的,它也可能具有嘈杂的功能(例如,在视觉应用程序中,客户端可能具有低分辨率的摄像头,其输出缩放到更高的分辨率)或嘈杂的标签(例如,如果用户无意中指出对某个应用程序的推荐不感兴趣)。由于这些损坏可以看作是模型更新和数据中毒攻击的温和形式,因此一种缓解策略是对防御模型更新和数据中毒攻击使用防御措施。另一种可能性是,标准的联合训练方法(例如联合平均 )固有地对少量噪声具有鲁棒性。
参考文献
- S. A. Rahman, H. Tout, H. Ould-Slimane, A. Mourad, C. Talhi and M. Guizani, “A Survey on Federated Learning: The Journey from Centralized to Distributed On-Site Learning and Beyond,” in IEEE Internet of Things Journal, doi: 10.1109/JIOT.2020.3030072.
- Kairouz, P., McMahan, H. B., Avent, B., Bellet, A., Bennis, M., Bhagoji, A. N., Bonawitz, K., Charles, Z., Cormode, G., Cummings, R., et al. Advances and open problems in federated learning. arXiv preprint arXiv:1912.04977, 2019.
- 杨强著.联邦学习[M].北京:电子工业出版社.2020.
- https://zhuanlan.zhihu.com/p/144358629
- https://github.com/tao-shen/Federated-Learning-FAQ/
-
AI应用开发实战 - 手写算式计算器
2018-09-28 09:01:07扩展手写数字识别应用 识别并计算简单手写数学表达式 主要知识点 了解MNIST数据集 了解如何扩展数据集 实现手写算式计算器 简介 本文将介绍一例支持识别手写数学表达式并对其进行计算的人工智能应用的...扩展手写数字识别应用
识别并计算简单手写数学表达式
主要知识点
- 了解MNIST数据集
- 了解如何扩展数据集
- 实现手写算式计算器
简介
本文将介绍一例支持识别手写数学表达式并对其进行计算的人工智能应用的开发案例。本文的应用是基于前文“手写识别应用入门”中的基础应用进行扩展实现的。本文将通过这一案例,展示基本的数据整理和扩展人工智能模型的过程,以及介绍如何利用手写输入的特性来简化字符分割的过程。并且本文将演示如何利用Visual Studio Tools for AI进行批量推理,以便利用底层人工智能框架的并行计算,实现推理加速。此外,本文还将对该应用的主要代码逻辑进行分析、讲解。
背景
在“手写识别应用入门”中,我们介绍了能识别单个手写字母、基于MNIST数据集的人工智能应用,并且在我们的几次试验中,该应用表现良好,能比较准确地将手写的数字图形识别成对应的数字。那么,该应用能不能识别更多种类的手写字符,甚至是同时的出现多个字符呢?这样的情形有很多,比如生活中常见的数学表达式(形如
1+2x3
)。这样的复合情形更为常见,也更具现实意义。相比之下,如果一次识别仅能一个手写数字,应用就会有比较大的局限性。首先,我们可以尝试一下多个字符同时出现这类情形中最基本的特例,即一次出现两个数字的情况。请启动手写数字识别博客中构建的应用,并在现有的应用里一次写下两个数字,看看识别效果(为了更方便书写及展示效果,我们将前一示例中笔画的宽度由40调整为20。可以体验出这一改动对单个数字的识别并无大的影响):
上图是一次试验的结果。进行多次试验,我们看到现有应用对两个数字的识别效果不尽人意。
如上图所示,应用窗口右上角展示的结果准确地反应了模型对我们手写输入的推理结果(即
result.First().First().ToString()
),然而这一结果并不像期望的那样,是我们在左侧绘图区写下的“42”。其实对这个现象的解释已经蕴含在我们之前的博客内容中了。在“手写识别应用入门”的模型介绍章节中,我们对用于训练模型的MNIST数据集做了大致的介绍。归根结底,上述现象的症结在于:作为我们人工智能应用核心的模型,本身并不具备识别多个数字的能力——作为模型的源头,也即是训练数据的MNIST数据集只覆盖了单个的手写数字。并且,在应用的输入处理部分,我们并未对笔迹图形作额外的处理。
这两点的综合结果就是,在写下多个数字的情况下,我们实际上在“强行”让AI模型做超出其适应性范围的推理。这属于AI模型的误用。其结果自然难以令人满意。
那么,为了增强应用的可用性,我们能不能改善这款应用,让其能处理常见的数学表达式呢?这要求我们的应用既能识别数字和符号,又能识别同时出现的多个字符:首先对于多个数字这种情况,我们很自然地想到,既然MNIST模型已经能很好地识别单个数字,那我们只需要把多个数字分开,一个一个地让MNIST模型进行识别就好了;对于识别其他数学符号,我们可以尝试通过扩展MNIST模型的识别范围,也即扩展MNIST数据集来实现。两者合二为一,就是一种非常可行的解决方案。这样,我们就引入了两个新的子问题,即“扩充MNIST数据集”和“多个手写字符的分割”。
结合上文陈述的问题和潜在的解决方案,本文将以“识别并计算简单的数学表达式”这一问题为导向,对现有的手写数字识别应用进行扩展。
我们的目标是对克服现存的只能对单个数字进行识别这一局限,让新应用可以识别数字、加减乘除和括号这些能构成简单数学表达式的元素,并对识别出的数学表达式进行计算。本文希望通过这些,能最终获得一款更具现实意义的人工智能应用。
最终的应用效果如下图:
注意
“识别可能出现的多种字符”和“识别同时出现的多个字符”是完全不同的,请注意区别。
子问题:扩展MNIST数据集
准备数据
数据格式
为了让我们的新模型能支持除了数字以外的字符,一个简单的做法是扩展MNIST数据集并尝试复用已有的模型训练算法(卷积神经网络)。在“手写识别应用入门”的数据预处理章节中,我们部分了解了MNIST数据集所采用的数据格式和规范。为了尽可能地复用已有的资源,我们有必要让扩展的那部分数据贴近原始的MNIST数据。
Samples-for-ai样例库中使用的MNIST示例,在初运行时会从http://yann.lecun.com/exdb/mnist/下载MNIST数据集并作为训练数据。当我们顺利运行mnist.py脚本并完成训练后,我们可以在
samples-for-ai\examples\tensorflow\MNIST\input
目录中看到四个扩展名为.gz
的文件,这四个文件就是从网上下载下来的MNIST数据集,即手写数字的位图和标记。不过这些文件是经过压缩的数据,我们使用的训练程序在下载完成后还会对这些压缩文件进行解压。训练程序只将解压后的数据储存在内存中,并没有回写到硬盘上,所以我们在input目录下找不到储存了原始位图数据的文件。小提示
我们仍可以使用支持这种压缩格式的工具将其解压。并使用二进制工具查看其内容。
从http://yann.lecun.com/exdb/mnist/页面上,我们可以了解到MNIST数据集的位图文件和标签文件的文件格式。其中最主要的,是用于训练的位图都是
28x28
尺寸的、单通道的灰度图,前景色(笔画)对应值为255(按颜色表示即是白色),背景色对应值0(按颜色表示即是黑色)。从之前博客中我们已经了解到,MNIST数据集是取反保存位图像素的,如果将其直接显示为位图,则和我们在界面上所见的白底黑字相反。结合页面上的描述和
mnist.py
中数据预处理部分的相关逻辑,我们了解到目前使用的卷积神经网络要求的最终的输入数据格式如下:图像数据 标记数据 四维数组 一维数组 第一维大小为输入的图片总数;
第二维、第三维大小为输入位图的宽高,此处皆为28;
第四维大小为输入位图的颜色通道数,MNIST只使用灰度图片,故为1。大小为输入的图片总数。 每个元素(在第四维)都是32位浮点数。取值大于等于-0.5,小于等于0.5。其中0.5表示前景像素的最大值,-0.5表示背景像素的最大值。 每个元素都是64位整数。取值0-9,分别代表对应的手写0-9的数字。 根据上述的输入格式,我们已经可以确定我们扩展训练数据的方向了。这里我们需要注意,这些格式是最终输入到卷积神经网络的数据必须满足的,而非我们即将搜集、准备的新数据。虽然这表明了我们新搜集的数据不一定要精确地满足这些条件,这些输入格式仍然对我们的数据搜集起到重要的指导作用。
收集并格式化数据
搜集数据的方式多种多样。就本文的需要来说,我们可以在网络上搜索已有的数据集,可以自行开发小型应用以在触摸屏甚至手机上搜集手写图形,或者扫描手写文档并通过图像分割的方式提取运算符。并且,在搜集完原始数据之后,我们还可以通过缩放、扭曲、添加噪点等方式来扩展、增强我们的数据集,以获得更广泛的适应性。
在我们搜集足够多的新图片后(考虑到原始MNIST数据集共70000张图片,我们搜集40000张左右比较合适,虽然数量不是绝对的),我们还需要对其进行一定的格式化,以方便我们最终将其作为神经网络的输入。
位图部分所需的处理非常直观。我们可以参考前一篇手写数字识别博客中对应用图形界面上捕获的手写图形的处理方式,将搜集的图片(可能具有RGB通道)转换成28x28像素的、单通道的灰度图片,并且前景色(即笔画)色值为0(黑色),背景色色值为255(白色)。符合要求的位图样例如下:
此处更需要注意的是对图片标记的处理。在原始MNIST数据集中我们看到整数0-9被用来标记对应的图形,这是非常自然的做法。因为我们此处要解决的是多分类问题,解决这类问题的一个先决条件就是我们必须为每个分类提供一一对应的标记。我们很容易就想到用诸如10、11、12来标记加号、减号、乘号等图形类别。这是可行的。
此处我们不由得思考,延续已被占用的自然数取标记新类别,虽然可行,但让对应关系变得混乱了。10和加号、11和减号之间,并不像0-9的整数和图形之间有那么自然的联系。作为开发者的我们不禁想到,能否用ASCII表里加减乘除的字符对应的数值来做标记呢(如加号对应53,减号对应55)?这种标记的设定方法实际上是很难使用的,特别是本文中出现的MNIST训练程序基于的是TensorFlow框架,框架本身要求了标记占用的整数值必须小于类别总数。在保证标记和类别一一对应的前提条件下,我们接着已有0-9标记,再为我们新搜集的图形类别增加标记。此时我们需要清楚的定义标记到类别的对应关系,以便我们正确处理模型的输入和输出。
我们用10-15分别表示加号、减号、乘号、除号、正括号、反括号。并且,为了便于训练,我们要求这六种数学符号对应的位图,分别放置于
add、minus、mul、div、lp、rp
这六个文件夹中,并且这六个文件夹需要在同一个目录下。如下图所示:训练模型
为了支持我们新增的六种数学符号,我们需要修改原始的MNIST模型训练脚本(即之前所用的
mnist.py
)。训练模型所需的Python脚本,可以在这里找到:
https://github.com/MS-UAP/edu/tree/master/AI301/self-built_mnist_extenstion
这一仓库中:
- 在
./tensorflow_model/
路径下,是支持扩展的MNIST数据集的训练脚本mnist_extension.py
。这一脚本要求额外的命令行参数--extension_dir
,用于指定我们扩展的六种数学符号的位图所在; - 在
./extended_mnist_calculator/MNIST.App
目录下,是本文这款应用的主体代码。我们会在下文中用到。
上文中,我们要求新搜集的数据最后需要被格式化为以色值0(黑色)为前景色,以色值255(白色)为前景色的单通道位图。我们修改后的训练脚本会读取这些位图并其反色,以到达和原始MNIST数据同样的效果(也和我们应用中输入处理的部分一致)。
假设我们存放add、minus
等六个文件夹的目录是D:\extension_images
,我们就可以在克隆好了的仓库的/training
目录下,通过命令行执行:python mnist_extension.py --extension_dir D:\extension_images
来启动针对包含了六种数学符号的扩展数据集的训练。该训练脚本在导入原始MNIST数据之后,还会从
D:\extension_images
目录分别读取六种新类别的数据。再混合新旧数据之后进行训练。可能的训练结果如下图:小提示
混合新旧数据在这里非常有用。因为训练过程中,目前的脚本是一次仅将一部分数据用于迭代优化和模型参数更新。如果不进行混合,就会发生新数据迟迟不被利用的情况,影响模型的训练结果。
我们对MNIST模型的训练是基于卷积神经网络的。并且上本中的脚本在处理扩展的符号位图之外,并没有对用于训练原始MNIST模型的卷积神经网络的结构进行修改。我们知道系统的结构决定其功能,那么我们针对原始MNIST数据设计的网络结构能否支撑扩展后的数据集呢?对这一问题最简单的回答就是进行一次训练并观察模型性能。
用这种方法进行试验后,我们通过错误率(主要是Validation error,在此例中反映了每100次小批量训练之后,模型当前在整个验证集上的错误率;和Test error,在此例中反映了训练结束后模型在整个测试集上的错误率)发现新模型的性能还是不错的。足以支持我们接下来的应用。
子问题:分割多个手写字符
如上文所述,我们为了对多个同时出现的字符进行识别,还必须解决一个子问题,那就是要对这些同时出现的字符进行分割。
我们注意到本文介绍的应用有一个特点,那就是最终用作输入的图形,是用户当场写下的,而非通过图片文件导入的静态图片。也就是说,我们拥有笔画产生过程中的全部动态信息,比如笔画的先后顺序,笔画的重叠关系等等。而且我们期望这些笔画基本都是横向书写的。考虑到这些信息,我们可以设计一种基本的分割规则:在水平面上的投影相重叠的笔画,我们就认为它们同属于一个数字。
笔画和水平方向上投影的关系示意如下图:
因此书写时,就要求不同的数字之间尽量隔开。当然为了尽可能处理不经意的重叠,我们还可以为重叠部分相对每一笔画的位置设定一个阈值,如至少进入笔画一端的10%以内。
加入对重叠的容忍阈值后,对笔画的分割的结果可以参看下图。在分割后被认为是属于同一字符的笔画我们使用了相同的颜色绘制,并且用不同的颜色区分了不属于同一字符的笔画。在字符的上方,我们用一系列水平方向的半透明色块表现了每一笔画在水平方向上的的有效重叠区域和字符之间的重叠关系。
应用这样的规则后,我们就能简便而又有效地对多个笔画进行分割,并能利用Visual Studio Tools for AI提供的批量推理功能,一次性对所有分割出的图形做推理。
应用的构建和理解
完成应用
同“手写识别应用入门”类似,我们还是先于GitHub克隆主体的应用代码,再加以引用模型来完成本文中这款应用。
按照训练模型一节中所述,获取上面提到的Git仓库后,我们可以通过Visual Studio打开
./extended_mnist_calculator
目录下的MnistDemo.sln解决方案,并和之前一样,在解决方案里添加AI Tools – Inference模型项目。不过与上一博客稍有不同的是,为了对我们扩展的新模型加以区分,我们需要将新模型项目命名为ExtendedModel(同时也是默认的命名空间名字),并将新的模型包装类命名为MnistExtension
。并且这一次,在模型项目创建向导中,我们需要选择上文中训练出的新模型。新的Inference模型项目和模型包装类配置如下图:
理解代码
输入处理
在新应用的代码部分,和我们在手写数字识别博客中介绍的代码比起来,差别最大的地方就在于如何处理输入。在上个案例中,我们只需要简单地将正方形区域中的图像格式调整一下,即可用作MNIST模型的输入。而在本文的案例中,我们必须先对笔画进行分割处理。分割笔画之后我们再将每一个笔画组合转换成MNIST模型所需的单个输入。
新应用需要响应的界面事件,还是和之前一致:需要响应鼠标的按下、移动和抬起三类事件。我们对其中按下和移动的响应事件的修改比较简单,我们只需要在这些响应时间里对新写下的笔画做记录就好了。
记录笔画的产生过程
首先我们为窗体类新增一个
List<Point>
类型的字段,用于记录每次鼠标按下、抬起之间鼠标移动过的点,将这些点按顺序连接起来就形成了一道笔画。我们在鼠标按下事件里清空以前记录的所有鼠标移动点,以便记录这次书写产生的新一动点;并在鼠标抬起事件里将这些点转换成笔画对应的数据结构StrokeRecord
(定义见后文)。同样的,我们也为窗体类新增一个List<StrokeRecord>
类型的字段,用于记录已经写下的所有笔画。private List<Point> strokePoints = new List<Point>(); private List<StrokeRecord> allStrokes = new List<StrokeRecord>();
在
writeArea_MouseDown
方法中新增以下语句用于清空以前记录的鼠标移动点:strokePoints.Clear();
并在
writeArea_MouseMove
方法中记录鼠标这次移动所到达的点:strokePoints.Add(e.Location);
在
writeArea_MouseUp
方法里将这次鼠标按下、抬起之间产生的所有点转换成笔画对应的数据结构。并且因为如果鼠标在抬起之前并没有移动,就不会有点被记录,在这之前我们还通过strokePoints.Any()
先判断一下是否有点被记录。下面是转化移动点的代码:var thisStrokeRecord = new StrokeRecord(strokePoints); allStrokes.Add(thisStrokeRecord);
包括构造函数在内的StrokeRecord结构定义如下:
/// <summary> /// 用于记录历史笔画信息的数据结构。 /// </summary> class StrokeRecord { public StrokeRecord(List<Point> strokePoints) { // 拷贝所有Point以避免列表在外部被修改。 Points = new List<Point>(strokePoints); HorizontalStart = Points.Min(pt => pt.X); HorizontalEnd = Points.Max(pt => pt.X); HorizontalLength = HorizontalEnd - HorizontalStart; OverlayMaxStart = HorizontalStart + (int)(HorizontalLength * (1 - ProjectionOverlayRatioThreshold)); OverlayMinEnd = HorizontalStart + (int)(HorizontalLength * ProjectionOverlayRatioThreshold); } /// <summary> /// 构成这一笔画的点。 /// </summary> public List<Point> Points { get; } /// <summary> /// 这一笔画在水平方向上的起点。 /// </summary> public int HorizontalStart { get; } /// <summary> /// 这一笔画在水平方向上的终点。 /// </summary> public int HorizontalEnd { get; } /// <summary> /// 这一笔画在水平方向上的长度。 /// </summary> public int HorizontalLength { get; } /// <summary> /// 另一笔画必须越过这些阈值点,才被认为和这一笔画重合。 /// </summary> public int OverlayMaxStart { get; } public int OverlayMinEnd { get; } private bool CheckPosition(StrokeRecord other) { return (other.HorizontalStart < OverlayMaxStart) || (OverlayMinEnd < other.HorizontalEnd); } /// <summary> /// 检查另一笔画是否和这一笔画重叠。 /// </summary> /// <param name="other"></param> public bool OverlayWith(StrokeRecord other) { return this.CheckPosition(other) || other.CheckPosition(this); } }
分割笔画
在将新产生的笔画添加到所有笔画的列表中之后,我们就有了当前用户写下的所有笔画了,接下来我们要对这些笔画进行分组。
本文在这里对上文所述的“快速”分割的实现非常简单。在按笔画在水平方向上最左端的坐标,将笔画有小到大排序后,我们从最左边开始扫描所有笔画。如果一个笔画还没有分组,我们就为它指定唯一分组编号,然后再看其右侧有哪些笔画和当前笔画在水平方向上的投影是有效重合的(如上文所述,此处有阈值10%),并将这些重合的笔画定为属于同一组。直到所有笔画都被扫描。
allStrokes = allStrokes.OrderBy(s => s.HorizontalStart).ToList(); int[] strokeGroupIds = new int[allStrokes.Count]; int nextGroupId = 1; for (int i = 0; i < allStrokes.Count; i++) { // 为了避免水平方向太多笔画被连在一起,我们采取一种简单的办法: // 当1、2笔画重叠时,我们就不会在检查笔画2和更右侧笔画是否重叠。 if (strokeGroupIds[i] != 0) { continue; } strokeGroupIds[i] = nextGroupId; nextGroupId++; var s1 = allStrokes[i]; for (int j = 1; i + j < allStrokes.Count; j++) { var s2 = allStrokes[i + j]; if (s2.HorizontalStart < s1.OverlayMaxStart) // 先判断临界条件(阈值10%) { if (strokeGroupIds[i + j] == 0) { if (s1.OverlayWith(s2)) // 在考虑阈值的条件下做完整地判断重合 { strokeGroupIds[i + j] = strokeGroupIds[i]; } } } else { break; } } }
之后即可按对应的分组编号将笔画归组:
List<IGrouping<int, StrokeRecord>> groups = allStrokes .Zip(strokeGroupIds, Tuple.Create) .GroupBy(tuple => tuple.Item2, tuple => tuple.Item1) // Item2是分组编号, Item1是StrokeRecord .ToList();
小提示
为了方便理解笔画的分割效果,应用界面上预留了“显示笔画分组”的开关。勾选之后写下的笔画会像上文那样被不同的颜色标记出其所在的分组。
为每个分组生成单一位图
分割完成后,我们得到了一个数组
groups
,它的每个元素都是一个分组,包括了分组编号和组内的所有笔画。这里我们得到的每一个分组都对应着一个字符。如果分组里有多个笔画,那么这些笔画就是这个字符的组成部分(想象加号和乘号,它们都需要两笔才能写成)。我们可以想到,这个数组groups
里的元素的顺序是很重要的,因为我们要保证最终识别出的表达式里的字符的顺序,才能正确地计算表达式。我们在循环中顺序访问
groups
的每个元素。命名循环变量为group
:foreach (IGrouping<int, StrokeRecord> group in groups)
循环变量
group
的类型是IGrouping<int, StrokeRecord>
,它代表着一个分组,包括分组的编号(一个整数)和其中的元素(元素都是StrokeRecord
)。IGrouping<TKey, TElement>
泛型接口同时也是一个可迭代的IEnumerable<TElement>
泛型接口,所以我们可以把group
变量直接当做IEnumerable<StrokeRecord>
类型的对象来使用。然后我们需要确定这个分组(即其中所有笔画组合成的图形)的位置区域,其中我们最关心水平方向上最左端、最右端的坐标(水平方向的坐标轴是从左向右的)。
通过这两个坐标我们就能确定该分组在水平方向上的投影的长度。我们计算这个长度的目的,是为了在我们为每个分组生成单一位图时,尽量将这个分组的图形放置在单一位图的中间位置。虽然我们还是先创建一个大尺寸的正方形位图(边长为绘图区高度),但是分割后的图形在这个正方形区域上不再具有天然的位置。下面的代码进行了这些位置的计算,和居中该分组所需的水平方向的偏移量的计算:
var groupedStrokes = group.ToList(); // IGrouping<TKey, TElement>本质上也是一个可迭代的IEnumerable<TElement> // 确定整个分组的所有笔画的范围。 int grpHorizontalStart = groupedStrokes.Min(s => s.HorizontalStart); int grpHorizontalEnd = groupedStrokes.Max(s => s.HorizontalEnd); int grpHorizontalLength = grpHorizontalEnd - grpHorizontalStart; int canvasEdgeLen = writeArea.Height; Bitmap canvas = new Bitmap(canvasEdgeLen, canvasEdgeLen); Graphics canvasGraphics = Graphics.FromImage(canvas); canvasGraphics.Clear(Color.White); // 因为我们提取了每个笔画,就不能把长方形的绘图区直接当做输入了。 // 这里我们把宽度小于 writeArea.Height 的分组在 canvas 内居中。 int halfOffsetX = Math.Max(canvasEdgeLen - grpHorizontalLength, 0) / 2;
之后我们就在新创建出的位图上绘制当前分组内的笔画了(通过
canvasGraphics
对象进行绘制):foreach (var stroke in groupedStrokes) { Point startPoint = stroke.Points[0]; foreach (var point in stroke.Points.Skip(1)) { var from = startPoint; var to = point; // 因为每个分组都是在长方形的绘图区被记录的,所以在单一位图上,需要先减去相对于长方形绘图区的偏移量 grpHorizontalStart from.X = from.X - grpHorizontalStart + halfOffsetX; to.X = to.X - grpHorizontalStart + halfOffsetX; canvasGraphics.DrawLine(penStyle, from, to); startPoint = point; } }
批量推理
在新应用中,我们一次需要识别多个字符。而以前我们一次只需要识别一个字符,哪怕我们每次都为了识别一个字符调用了一次模型的推理方法(
model.Infer(...)
)。不过我们现在已经准备好了多组数据,这使得我们有机会利用底层AI框架的并行处理能力,来加速我们的推理过程,还省去了手动处理多线程的麻烦。在这里我们采用Visual Studio Tools for AI提供的批量推理功能,一次对所有数据进行推理并得到全部结果。
首先我们在为所得分组创建位图之前,需要先创建一个用于储存所有数据的动态数组:
var batchInferInput = new List<IEnumerable<float>>();
在处理所有分组的循环内部,处理完每个分组后,我们需要将该分组对应的像素数据暂时存放在动态数组
batchInferInput
中:// 1. 将分割出的笔画图片缩小至 28 x 28,与训练数据格式一致。 Bitmap clonedBmp = new Bitmap(canvas, ImageSize, ImageSize); var image = new List<float>(ImageSize * ImageSize); for (var x = 0; x < ImageSize; x++) { for (var y = 0; y < ImageSize; y++) { var color = clonedBmp.GetPixel(y, x); image.Add((float)(0.5 - (color.R + color.G + color.B) / (3.0 * 255))); } } // 将这一组笔画对应的矩阵保存下来,以备批量推理。 batchInferInput.Add(image);
可以看到我们对每个分组的处理,都和以前对整个正方形绘图区的像素的处理,是完全一致的。唯一的不同是在以前的应用代码中,
List<IEnumerable<float>>
类型的数组(在上文中为batchInferInput
变量)仅有一个元素,就是唯一一张位图的像素数据。而在本文中这个数组可能有很多元素,每个元素都是一组位图数据。对这样的位图数据集合进行批量推理后,得到的结果(即inferResult
变量)是一个可枚举的类型,我们叫它“第一层枚举”。第一层枚举得到的每个元素也是一个可枚举类型,我们叫它“第二层枚举”。第一层枚举中的每个元素都对应着一组位图数据的推理结果。同时第一层枚举也是对应着批量推理的输入数组,枚举的结果总数和输入数组的长度相同。对于第二层枚举,由于我们的推理结果只是一个整数,所以第二层枚举总是只有一个元素。我们可以通过
.First()
将其取出。这里我们可以看到,在以前的应用代码里,我们通过inferResult.First().First()
取出了唯一的结果,而在这里我们则需要考虑批量推理结果的二维结构。进行推理的代码如下:
// 2. 进行批量推理 // batchInferInput 是一个列表,它的每个元素都是一次推量的输入。 IEnumerable<IEnumerable<long>> inferResult = model.Infer(batchInferInput); // 推量的结果是一个可枚举对象,它的每个元素代表了批量推理中一次推理的结果。我们用 仅一次.First() 将它们的结果都取出来,并格式化。 outputText.Text = string.Join("", inferResult.Select(singleResult => singleResult.First().ToString()));
计算表达式
至此,我们对于多个手写字符的识别就完成了。我们已经得到了可以表示用户手写图形的、易于计算机程序处理的字符串。接下来我们开始对字符串记载的数学表达式进行计算。
本文需要计算的数学表达式的格式,由上文的数据准备和模型训练部分可知,是相对简单的。其中只涉及数字0-9、加减乘除和小括号。对这样的表达式进行求值,是一种非常典型的问题。因为这样的数学表达式有非常清晰、确定的语法规则,对其最直观的处理方法,就是先根据其语法进行解析,构造语法树后进行求值即可。或者,因为这种问题非常经典,我们也可以寻找已有的组件来解决这个问题。
本文直接复用
System.Data.DataTable
类提供的Compute
方法来进行表达式的计算。这个方法完全支持本文案例中出现的表达式语法。因为表达式的计算这部分逻辑边界非常清晰,我们引入一个独立的方法来获取最后的结果:
string EvaluateAndFormatExpression(List<int> recognizedLabels)
EvaluateAndFormatExpression
方法接受一个标签序列,其中我们仍在用整数10-15来表示各种数学符号。在这个方法内我们对字符标签做两种映射,分别将标签序列转换成用于输入到计算器进行求值的,和用于在用户界面上展示的。EvaluateAndFormatExpression
方法的返回结果形如“(3+2)÷2=2.5”。其中各种符号皆采用传统的数学写法。该方法的实现如下:private string EvaluateAndFormatExpression(List<int> recognizedLabels) { string[] operatorsToEval = { "+", "-", "*", "/", "(", ")" }; string[] operatorsToDisplay = { "+", "-", "×", "÷", "(", ")" }; string toEval = string.Join("", recognizedLabels.Select(label => { if (0 <= label && label <= 9) { return label.ToString(); } return operatorsToEval[label - 10]; })); var evalResult = new DataTable().Compute(toEval, null); if (evalResult is DBNull) { return "Error"; } else { string toDisplay = string.Join("", recognizedLabels.Select(label => { if (0 <= label && label <= 9) { return label.ToString(); } return operatorsToDisplay[label - 10]; })); return $"{toDisplay}={evalResult}"; } }
同时需要注意的是,根据表达式求值方案的不同,我们可能需要对表达式中的字符进行对应的调整。比如当我们希望在用户界面上将除号显示为更可读的“÷”时,我们采用的求值方案可能并不支持这种除号,而只支持C#语言中的除号
/
。那么我们在将识别出的结果输入到表达式计算器中之前,还需要对识别的结果进行合适的映射。常见问题
新模型对括号和数字1的识别很差
这是一种非常容易出现的情况。因为在手写时,正反小括号和数字1极易混淆。这一问题有时会在扩展数据中体现。我们观察到原始MNIST数据集中(参见上文的数据可视化),很多数字1的形状和弯曲程度已经和括号相近。如果我们在扩展数据部分不做明显的区分,并且我们采用的卷积神经网络对这样微小的数据差别不敏感的话,就会导致造型相近的字符被错误识别的情况。
同理,这样的问题还可能发生在加号和乘号之间。因为加号和乘号的形状基本完全一样,只是靠角度得以区分。如果我们搜集的扩展数据里,这两种符号各自都具有一定的旋转角度,以致角度区分不够明显,这也会导致模型对其识别能力不强的情况出现。
扩展问题
经过一番扩展,我们的新应用已经具备一些不错的功能,初步满足了现实规格的应用需求。从本文的案例中,我们也能得到关于如何将人工智能和传统的技术手段融合起来,帮助我们更好地解决问题的一些启示。当然,这款新应用仍然不够强大和健壮。对此,我们注意到有这样一些问题仍待解决:
- 笔画分割的算法相对比较简单、粗糙,如何提升整体的分割效果,以顺利处理重叠、连笔、噪点等可能情况?
- 作为一款计算器应用,本文介绍的新应用具备的特性还是很少。如何增加新的数学计算特性,比如开根号、分数或者更多的数学符号?
欢迎使用Markdown编辑器
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + Shift + B
斜体:Ctrl/Command + Shift + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的
代码片
.// An highlighted block var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 Value 电脑 $1600 手机 $12 导管 $1 设定内容居中、居左、居右
使用
:---------:
居中
使用:----------
居左
使用----------:
居右第一列 第二列 第三列 第一列文本居中 第二列文本居右 第三列文本居左 SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE ASCII HTML Single backticks 'Isn't this fun?'
‘Isn’t this fun?’ Quotes "Isn't this fun?"
“Isn’t this fun?” Dashes -- is en-dash, --- is em-dash
– is en-dash, — is em-dash 创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞tz−1e−tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::
这将产生一个流程图。:
graph LR A[长方形] -- 链接 --> B((圆)) A --> C(圆角长方形) B --> D{菱形} C --> D
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
注脚的解释 ↩︎
-
集成光学的应用大有潜力
2021-02-08 13:05:19从最近佛罗里达州奥兰多召开的集成-波导光学专题会议以及在加州...多年来集成光学已能提供高效率、低功率、密集型、大批量的光电波导器件,目前有直接应用前景的三个主要领域是:远距离通信、高速信号处理和光纤传感。 -
浅谈MFC应用前景,第一课
2016-10-11 23:46:53从今天起,我将开始说一些关于mfc应用的编程,这一篇不说别的,主要是说说mfc的前景问题,解决一些朋友们的顾虑问题。 很多人开始学windows编程了,但是有一个顾虑就是是否要学习mfc,有的朋友估计也在网上查了... -
深度学习在医疗中的应用前景分析
2016-07-25 11:55:03总结了深度学习在医疗领域成功应用的领域,并预测以限制性波尔兹曼机(RBM)为基础的深度信念网络(DBN)在医学诊断中的应用,由于可以促近近期医改中的分级诊疗的发展,将拥有广阔的应用前景。 -
大数据专业和人工智能专业哪个前景更好
2021-01-14 06:35:53首先,人工智能和大数据这两个专业的前景都比较广阔,随着产业结构升级的持续推进,未来大数据和人工智能专业的人才培养规模会逐渐扩大。人工智能与大数据具有密切的联系,大数据是人工智能的重要基础,二者之间的... -
什么编程语言比较适合开发桌面应用程序?
2021-05-22 10:11:01如果是Windows系统,那么C#无疑是首选,如果考虑到跨平台,那么C++可能更合适,下面我分别简单介绍一下,感兴趣的朋友可以尝试一下:C#这是微软自主设计研发的一款编程语言,在Windows环境下有着广泛的应用,... -
Python语言在未来的发展前景
2020-12-11 17:16:47因为这个语言的前景是不可限量的,而且他的语法非常的简单易懂,这就让很多一些提及编程就恐慌的人减去了担心,现在已经是一人应该掌握一门编程语言的时代,很多不是程序员的人们,利用自己写的简单的小程序,让自己... -
一线工程师告诉你嵌入式真实现状与发展前景
2018-10-02 18:49:59个人说明:本人并不是年薪百万的技术大牛,但总算是一名合格的嵌入式工程师,现在某企业担任嵌入式软件工程师开发一...百度搜索“嵌入式”、“嵌入式开发”、“嵌入式发展前景”等字眼,出来的都是一大堆培训机构,... -
一文分析 Android现状及发展前景
2021-11-17 19:11:31“低头干活,还要抬头看路”,写一篇文章简要审视一下Android的发展现状、展望一下Android的发展前景。 Android 诞生背景 Android 发展现状 Android 前景趋势 从Android的诞生背景开始说起,举例一些数据看一下... -
高速数据采集卡的未来发展前景
2019-01-06 15:47:13高性能存储技术支持高达1.4GB/S高速写盘,支持高达每秒百万数量级的高频脉冲连续存储,时间可达数小时,专用数据管理模块记录信号的各种相关参数 10bit 5GSPS高速数据采集卡 产品编号:MR-10B-5G 产品类别... -
UI设计在中国的现状和发展前景如何?.doc
2020-09-21 01:06:13UI设计在中国的现状和发展前景如何 目前在国内UI还是一个相对陌生的词即便是一些设计人员也对这个词不太了解我们经常看到一些招聘广告写着招聘界面美工界面美术设计师等等这表明在国内对UI的理解还停留在美术设计... -
OA办公自动化系统在企业中的实际应用价值
2018-12-20 09:28:15摘要:以包头第三热电厂办公自动化系统的实施和应用为例,阐述OA 系统在企业中实际应用价值和应用中存在的问 题,探讨协同办公软件的发展前景。 关键词:电厂;办公自动化;实践应用 -
ROS(机器人操作系统)在国内前景如何?
2021-06-12 00:55:19即使openCV,PCL库提供的方法,也基本满足不了研究的需要,很多时候我也只是沿用他们的文件格式,做简单的visualization,写自己的算法。编程语言和平台终究都是工具,只要合理即可。谢邀。 史雪松: 工业机器人不... -
DSP+FPGA的前景及应用调研
2019-01-31 16:08:36DSP+FPGA处理系统正广泛应用于复杂的信号处理领域。在雷达信号处理、数字图像处理等领域中,信号处理的实时性至关重要。由于FPGA芯片在大数据量的底层算法处理上的优势及DSP芯片在复杂算法处理上的优势,DSP+FPGA的... -
展望2018:WebRTC技术现状、应用开发与前景
2018-01-26 10:49:37很多同学对WebRTC的背景、目的、意义以及限制其实并不明白,加上媒体上各种吹捧和质疑的声音互相掺杂,对WebRTC这项技术的应用前景和开发难度没有切实的判断。本文希望通过对WebRTC技术的粗浅梳理,为大家提供参考。... -
“你们程序员不就是修电脑的吗,你牛什么牛,移动应用开发专业就业前景
2022-01-22 16:56:20**很多专业性的书籍,都写的非常好,也非常全面,因为是文字性的描述,所以相对于视频学习起来时间成本更低。但书籍的内容质量也是良莠不齐,除去其他同学的推荐,自己很难真正感悟到书籍的内容对自己的帮助。这时候... -
Android应用图标微技巧,8.0系统中应用图标的适配
2018-03-13 07:56:38也就是说,我们在设计应用图标的时候,需要将前景和背景部分分离,前景用来展示应用图标的Logo,背景用来衬托应用图标的Logo。需要注意的是,背景层在设计的时候只允许定义颜色和纹理,但是不能定义形状。 那么... -
嵌入式到底应该选择驱动开发,还是应用开发?
2022-01-01 16:33:22嵌入式到底应该选择驱动开发,还是应用开发? -
人工智能技术在教育领域中的应用
2022-04-08 08:33:52那么今天海森大数据就和大家一起来讨论关于人工智能技术在教育行业的一些应用现状。 1.早教机器人 随着当前儿童经济的盛行,儿童教育行业消费在家庭总体消费中所占的比例在逐渐增大。近年来幼儿教育行业发展迅速... -
Android 开发的现状及发展前景
2020-10-23 17:23:10在几年前的时候,曾听过很多人说 Android 学习很简单,做个App就上手了,工作机会多,毕业后也比较容易找工作。这种观点可能是很多Android开发者最开始入行的原因之一。 在工作初期,工作主要是按照业务需求实现App... -
医疗人工智能前景——医学影像
2020-05-21 22:06:12写这篇文章梳理一下学习思路,也希望可以给刚开始进入这个行业的朋友一个quick guide。 从科研来看,深度学习在医学图像分割的传统应用上,如分割、配准等已经有疲软之势,基本都是用U-Net糅合来去。MICCAI是医学... -
uni-appの发展和应用
2022-03-07 09:40:28uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架,开发者编写一套代码,可编译到iOS、Android、H5、小程序等多个平台。uni-app 继承自 Vue.js,提供了完整的 Vue.js 开发体验。uni-app 组件规范和扩展api与微信... -
Go语言的前景、优点
2022-04-06 11:16:48Go语言的前景、优点 go是google开源的编程语言,诞生于2006年,2012年发布稳定版。有道是背靠大树好乘凉。作为大厂设计开发的语言,它拥有更好的背景以及发展。 go自带GC,因此程序员不需要操心内存的释放与回收。... -
人工智能在生活中的应用都有哪些?
2020-08-10 08:42:47今天小宅就来和大家一起分享一下人工智能的发展史及应用: 人工智能无处不在 人工智能的发展历程 · 1945艾伦图灵在论文《计算机器不智能》中提出了著名的图灵测试,给人工智能的収展产生了深远的影响。 · ... -
Java的前景如何,好不好自学?
2020-06-15 20:09:37首先来回答,Java 的前景如何? 在我眼里,Java 绝对是世界第一编程语言啊,PHP 表示不服,那也没办法啊,来看 TIOBE 这个编程语言排行榜吧。 上面这幅图是几个热门编程语言(Java、C、Python、JavaScript 等等)近...