• 每个周都会有人问,“你们可以在云端运行这些Android游戏吗?”,这很明显是一个非常具有吸引力的想法。不要客户端,游戏就能直接在智能电视、机顶盒或者GamePop,GameStick等微主机上运行,制造商和无线网络公司也...
    
    每个周都会有人问,“你们可以在云端运行这些Android游戏吗?”,这很明显是一个非常具有吸引力的想法。不要客户端,游戏就能直接在智能电视、机顶盒或者GamePop,GameStick等微主机上运行,制造商和无线网络公司也对手游登陆电视平台可能带来的收入前景感到期待,尤其是他们可以把手游体验传播到很偏远的地方。Ouya最近宣布将尝试移动云游戏服务,毫无疑问的是,这个决定非常有必要。

    云游戏技术对于安卓系统来说是否可行?

      但是,云游戏对于Android来说可行吗?Facebook,King,Kongregate以及其他开发商的大多数盈利业务都是网页平台的Flash游戏,这些在某种程度也可以称之为云游戏,但是,我们这里说的是探索手游进入云游戏领域的可能性,很明显两者是有很大区别的。

      OnLive的创始人Steve Perlman曾在斯坦福大学对自己的服务展示了一个动画,不得不说,这是20多年来最优秀的动画展示之一,他的展示让观众们目瞪口呆,他和一个朋友一起体验了一些对画面要求较高的PS3游戏,而且没有丝毫延迟,在他展示的屏幕上,所有人都可以进行实时游戏。

      当然,在大多数的展示中,云服务是一直提供支持的。这毕竟看起来还是非常神奇的事情,如今,视频压缩和流媒体技术的进步使得互联网之间的传播更加容易,如果你的网络没有延迟的话,那么云游戏体验无疑非常优秀。在像韩国,新加坡甚至美国的部分地区,云游戏服务都是可行的。

      技术障碍

      Amazon在2011年3月份把Test Drive服务发布到了自己的Android应用商店,但Test Drive选择的应用数量是极其有限的,比如Clash Of Clans,Candy Crush或者Deer Hunter这些手游大作都不能使用该服务,这主要是因为,当今大多数的手机芯片是基于ARM技术的,这些游戏需要ARM代码,而Amazon Test Drive则是基于x86技术,并非ARM架构。目前的ARM服务器已经很少了,x86平台只有两家公司运行ARM技术的Android应用,那就是英特尔和BlueStacks,如果没有这两家公司的其中之一的帮助,那么排名前100的手游中,有20%是不能在云端运行的。

      这种问题只有2种解决方案:

      使用ARM服务器创建云游戏,这需要技术,但并不一定能够挣钱,因为代价较高。

      所有顶级发行商们都需要重写游戏代码,开发商们更愿意研发新的等级或者新游戏,而不是想去重写旧游戏的代码,这个问题是很难解决的。这些技术方面的难题决定了大多数OnLive平台的云游戏都是为英特尔平台研发的很老的PC游戏。

      另一个拦路虎就是成本。如果让云游戏成为现实,那么开发商就需要让每个服务器支持更多的玩家,对于PC类游戏来讲,云游戏提供商之一的曾经说,他们可以同时让一个服务器支持8个玩家,当然,我们乐观点想,可以支持20个玩家,那么对于1000个实时玩家来说,你就需要50个服务器,,在英特尔的平台运行ARM游戏会大大增加需要的容量,几乎是之前的五倍。谷歌正准备用NaCl技术解决这一问题,但似乎还需要数年才能有结果。服务器成本几乎是多少流行Android游戏所不能够承受的。

      隐藏成本较高

      我们可以设想,如果服务器问题得到了解决。宽带费用也是另一个比较头疼的问题。为了运行这些游戏,你需要8Mbps的宽带,如果每个玩家只用5分钟的时间玩Candy Crush,也就是说你需要给每个玩家准备300MB,一百万DAU就需要30万GB的数据,而每GB在Amazon或者谷歌的云服务的收费是10美分,所以,开发商每天为100万DAU就要花费3万美元的成本。

      在手游领域,大多数游戏都采用的是免费模式,对于这些游戏的开发商来讲,只有2%的玩家是付费的。也就是说,100万DAU中只有2万人会付费,如果他们每人每天1美元的话,开发商每天的收入才2万美元,去掉了收入分成之后只有1.4万美元,但和每天要支付的宽带费用相比,开发商是很难生存的。

      有人会说,如果有5%的玩家付费,而且服务器成本可以降低的话会怎么样。但大多数的玩家都不会在进入游戏五分钟以后就开始付费,很少会有这样的玩家,玩家们的付费周期相对较长。比如目前收入排名较高的游戏,Clash Of Clans,Candy Crush以及赌博游戏,人们用了多久才付费?如果这些开发商把游戏放到云端运行的话,他们的成本就会把自己拖垮,公司也早该关门大吉了。

      另外,对于手游平台来讲,并不是每一款游戏都能取得King或者Supercell这样的成功,只有少数游戏是赚了大钱的,那么赔钱的游戏公司又有谁会使用呢?

      每个技术问题都会慢慢解决,我们看到过OnLive这样的公司。你或许能为一个PC游戏做云游戏服务,但是对于ARM架构的免费手游来说,还有非常多的技术障碍需要解决。

      (本文英文作者Rosen Sharma是BlueStacks公司CEO,该公司是App Player和GamePop产品的制造商。在加入BlueStacks之前,Sharma和其他人分别共同创建了Cloud.com(后来被Citrix收购),GreenBorder(后来被谷歌收购),Vxtreme(被微软收购),Teros(被Citrix收购)以及Solidcore(被McAfee收购),之后他在McAfee一直担任CTO兼高级副总裁,直到创建BlueStacks,Sharma拥有斯坦福和康纳尔大学的电脑网络博士学位)

    展开全文
  •  Posted by Cristian on 2013-11-26 ...GameLook报道/每个周都会有人问,“你们可以在云端运行这些Android游戏吗?”,这很明显是一个非常具有吸引力的想法。不要客户端,游戏就能直接在智能电视、机顶盒或者
    

    GameLook报道/每个周都会有人问,“你们可以在云端运行这些Android游戏吗?”,这很明显是一个非常具有吸引力的想法。不要客户端,游戏就能直接在智能电视、机顶盒或者GamePop,GameStick等微主机上运行,制造商和无线网络公司也对手游登陆电视平台可能带来的收入前景感到期待,尤其是他们可以把手游体验传播到很偏远的地方。Ouya最近宣布将尝试移动云游戏服务,毫无疑问的是,这个决定非常有必要。

    但是,云游戏对于Android来说可行吗?Facebook,King,Kongregate以及其他开发商的大多数盈利业务都是网页平台的Flash游戏,这些在某种程度也可以称之为云游戏,但是,我们这里说的是探索手游进入云游戏领域的可能性,很明显两者是有很大区别的。

    OnLive的创始人Steve Perlman曾在斯坦福大学对自己的服务展示了一个动画,不得不说,这是20多年来最优秀的动画展示之一,他的展示让观众们目瞪口呆,他和一个朋友一起体验了一些对画面要求较高的PS3游戏,而且没有丝毫延迟,在他展示的屏幕上,所有人都可以进行实时游戏。

    当然,在大多数的展示中,云服务是一直提供支持的。这毕竟看起来还是非常神奇的事情,如今,视频压缩和流媒体技术的进步使得互联网之间的传播更加容易,如果你的网络没有延迟的话,那么云游戏体验无疑非常优秀。在像韩国,新加坡甚至美国的部分地区,云游戏服务都是可行的。

    技术障碍

    Amazon在2011年3月份把Test Drive服务发布到了自己的Android应用商店,但Test Drive选择的应用数量是极其有限的,比如Clash Of Clans,Candy Crush或者Deer Hunter这些手游大作都不能使用该服务,这主要是因为,当今大多数的手机芯片是基于ARM技术的,这些游戏需要ARM代码,而Amazon Test Drive则是基于x86技术,并非ARM架构。目前的ARM服务器已经很少了,x86平台只有两家公司运行ARM技术的Android应用,那就是英特尔和BlueStacks,如果没有这两家公司的其中之一的帮助,那么排名前100的手游中,有20%是不能在云端运行的。

    这种问题只有2种解决方案:

    使用ARM服务器创建云游戏,这需要技术,但并不一定能够挣钱,因为代价较高。

    所有顶级发行商们都需要重写游戏代码,开发商们更愿意研发新的等级或者新游戏,而不是想去重写旧游戏的代码,这个问题是很难解决的。这些技术方面的难题决定了大多数OnLive平台的云游戏都是为英特尔平台研发的很老的PC游戏。

    另一个拦路虎就是成本。如果让云游戏成为现实,那么开发商就需要让每个服务器支持更多的玩家,对于PC类游戏来讲,云游戏提供商之一的曾经说,他们可以同时让一个服务器支持8个玩家,当然,我们乐观点想,可以支持20个玩家,那么对于1000个实时玩家来说,你就需要50个服务器,,在英特尔的平台运行ARM游戏会大大增加需要的容量,几乎是之前的五倍。谷歌正准备用NaCl技术解决这一问题,但似乎还需要数年才能有结果。服务器成本几乎是多少流行Android游戏所不能够承受的。

    隐藏成本较高

    我们可以设想,如果服务器问题得到了解决。宽带费用也是另一个比较头疼的问题。为了运行这些游戏,你需要8Mbps的宽带,如果每个玩家只用5分钟的时间玩Candy Crush,也就是说你需要给每个玩家准备300MB,一百万DAU就需要30万GB的数据,而每GB在Amazon或者谷歌的云服务的收费是10美分,所以,开发商每天为100万DAU就要花费3万美元的成本。

    在手游领域,大多数游戏都采用的是免费模式,对于这些游戏的开发商来讲,只有2%的玩家是付费的。也就是说,100万DAU中只有2万人会付费,如果他们每人每天1美元的话,开发商每天的收入才2万美元,去掉了收入分成之后只有1.4万美元,但和每天要支付的宽带费用相比,开发商是很难生存的。

    有人会说,如果有5%的玩家付费,而且服务器成本可以降低的话会怎么样。但大多数的玩家都不会在进入游戏五分钟以后就开始付费,很少会有这样的玩家,玩家们的付费周期相对较长。比如目前收入排名较高的游戏,Clash Of Clans,Candy Crush以及赌博游戏,人们用了多久才付费?如果这些开发商把游戏放到云端运行的话,他们的成本就会把自己拖垮,公司也早该关门大吉了。

    另外,对于手游平台来讲,并不是每一款游戏都能取得King或者Supercell这样的成功,只有少数游戏是赚了大钱的,那么赔钱的游戏公司又有谁会使用呢?

    每个技术问题都会慢慢解决,我们看到过OnLive这样的公司。你或许能为一个PC游戏做云游戏服务,但是对于ARM架构的免费手游来说,还有非常多的技术障碍需要解决。

    (本文英文作者Rosen Sharma是BlueStacks公司CEO,该公司是App Player和GamePop产品的制造商。在加入BlueStacks之前,Sharma和其他人分别共同创建了Cloud.com(后来被Citrix收购),GreenBorder(后来被谷歌收购),Vxtreme(被微软收购),Teros(被Citrix收购)以及Solidcore(被McAfee收购),之后他在McAfee一直担任CTO兼高级副总裁,直到创建BlueStacks,Sharma拥有斯坦福和康纳尔大学的电脑网络博士学位)

    展开全文
  • Aid Learning FrameWork是一个在Android手机上运行的带图形界面的Linux系统,用于AI编程。这意味着当它安装时,你的Android手机拥有一个可以在其中运行AI程序的Linux系统。现在我们有力地支持Caffe,Tensorflow,...

    转载自:https://www.cnblogs.com/aidlux/p/10853013.html

    这是一个伟大的APP:

    Aid Learning FrameWork是一个在Android手机上运行的带图形界面的Linux系统,用于AI编程。这意味着当它安装时,你的Android手机拥有一个可以在其中运行AI程序的Linux系统。现在我们有力地支持Caffe,Tensorflow,Mxnet,ncnn,Keras,cv2,Git / SSH这些框架。此外,我们提供了一个名为Aid_code的AI编码开发工具。它可以通过在我们的框架上使用Python来为您提供可视化的AI编程体验!

    现在你有了一个完整的Linux系统,可以在Android上运行了图形界面(这是一个真正的linux运行在busybox而不是虚拟环境。所以它更快,几乎是实时的。)并且可以直观地一键运行你的AI代码!

    现在已开源:https://github.com/aidlearning/AidLearning-FrameWork

    强大

    Caffe,Tensorflow,Mxnet,ncnn,Keras ......几乎内置所有的AI框架,你不需要复杂地配置AI框架。全部构建于一体!

    图形用户界面

    我们为Andorid上的Linux修复了图形用户界面(它已经被Andorid修剪了!),所以你可以像在电脑上一样使用GUI。例如,您可以使用opencv打开并查看相机!

    实时性强

    真正的linux在busybox上运行而不是像VirtulBox这样的虚拟环境。所以它更快,几乎是实时的

    使用方便

    我们提供了大量示例,通过使用我们的框架,您可以通过点击运行它,然后获取可视日志以显示信息或错误。

    随处开启编程模式

    您可以随时随地在手机上进行编码。每一寸碎片都得到了充分利用。通过灵感的闪现,您的创造力可以立即实现。

    能源之星--省电

    根据三星、华为这样的主流智能手机的测试,Aid Learning Framework一天只消耗1%的功耗(待机)

    适用于AI算法实验的最佳移动应用程序

    当您在Python中完成AI测试算法时,您不想在手机上进行测试吗?您不需要更改代码,也不需要使用JNI来开发应用程序,只需将Python中的算法文件复制到手机中,就可以立即使用我们的框架运行它。

    了解更多立即 获取应用

    这一切都与促进您的业务有关

    使用我们的框架,您可以在不使用Android Studio的情况下开发您的应用程序,并且可以直接在python中生成应用程序,这可以节省大量时间。此外,我们还内置了很多人工智能的例子,包括人脸识别、手势识别、身体姿势识别、物体识别等。

    我们最好的特征

    屏幕太小?

    你可以将手机屏幕投影到电视机上,然后在大电视屏幕上显示你的人工智能应用程序。或者,您可以使用sshd(已经内置)连接到PC,使用PC键盘进行编码。

    代码传输?

    您的SD卡目录已加载到/SD卡中,因此您可以使用USB线将代码传输到PC或其他设备。

    代码重用?

    我们使用最流行的语言python对ai应用程序进行编码,这样应用程序的代码也可以在PC上运行,你不需要修改。

    安全?

    辅助学习框架不需要根权限,不用担心破坏手机系统和数据。框架是独立的。

    慢?

    我们在框架中重新编译了多线程加速库openblas,使其快速、并行。

    云?

    框架不需要网络来工作,可脱机运行。当然,我们在云端中有很多例子。

    现在在测试时间。所以你可以免费获得它!

     

     

     

     

     

     

    展开全文
  • 在云端

    2008-12-09 16:36:00
    …………………………………………在云端遇见你的地方在生命的转弯你让我有天堂能想像在云端你深邃的目光从世界另一端最神秘的磁场唉呀呀唉唷梦一场…………………………………………周华健的一首《在云端》,相信...

    ……………………

    ……………………

    在云端遇见你的地方
    在生命的转弯
    你让我有天堂能想像
    在云端你深邃的目光
    从世界另一端
    最神秘的磁场
    唉呀呀唉唷梦一场<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    ……………………

    ……………………

    周华健的一首《在云端》,相信很多人都听过,今天的话题正好是跟“云”有关,只不过不是蓝天白云,而是隐藏在互联网背后的云-------云计算(Cloud Computing)

    什么是云计算?下面我引用李开复的一段话:

    “在云计算的模式中,用户所需的应用程序并不运行在用户的个人电脑、手机等终端设备上,而是运行在互联网上大规模的服务器集群中。用户所处理的数据也并不存储在本地,而是保存在互联网上的数据中心里。提供云计算服务的企业负责管理和维护这些数据中心的正常运转,保证足够强的计算能力和足够大的存储空间可供用户使用。而用户只需要在任何时间、任何地点,用任何可以连接至互联网的终端设备访问这些服务即可。

    云计算在给我们诠释一种回归,计算机最初的发展阶段是单机时代,很多用户通过一个个终端来分享计算机资源,终端非常简单,无非是可供输入的键盘和可供显示的显示器。后来PC的出现及普及把计算任务转移到了个人电脑上,个人电脑的配置越来越高,功能越来越强大,C/S(客户端/服务器)编程模式应运而生,在这个编程模式里面,我们的原则是能在Client(客户)端做的尽量在客户端做,尽量不要增加服务器的额外负担。随手举几个C/S的例子:几乎每个人都在用的QQ,MSN等即时聊天工具,大部分的炒股软件等。但是这种模式有一个问题:因为客户端也要完成大量的计算任务,当客户端出现问题的时候,如何维护将成为一个很严峻的问题,因为大多数客户对计算机的了解并不多,而有可能分布在全球各地。维护成本的增加是这种编程模式淡出历史舞台的主要原因。后来人们越来越倾向于“瘦客户端“,目前最瘦的客户端莫过于浏览器了,于是B/S(浏览器/服务器)编程模式越来越受人青睐,因为这种模式可以让开发者把精力都集中在服务端的建设上。所有的维护工作都可以在家里完成,节约了成本。用户也乐意接受,因为不需要再象以前一样装各种客户端,装软件并不是每个客户都擅长的事情。但因为受制于浏览器的功能,用户体验方面B/S仍不能完全替代C/S,所以目前仍然有很多应用是用C/S开发的,但这种变革是一种必然的趋势。

    云计算的出现给这种变革以巨大的推动力,因为它将使得这种B/S的应用更符合客户的利益,数据的存储和计算都是由专业服务商来提供的,如果我要建设一套公司内部的管理系统,只需要注册一个帐户就可以了,然后按月或者按年向服务商缴纳一定的使用费用即可。不必再自己购买服务器,购买专门的软件,不必再聘用专门的维护人员,因为有更专业的服务商在背后为客户服务。同时这也给我们这些开发者提了个醒:为什么有些技术大行其道?答案很简单,因为它更加符合客户的利益。

    在这种趋势下,未来我们需要的硬件应该越来越简单,只需要它能够支撑起一个功能足够强大的浏览器的运行就可以,我们的每个终端,PC、手机等电子设备作为云端接入到互联网的云计算网络就可以享受到各种体验。甚至是我们家里的每一样电器都可以作为云端,我跟我的搭档曾经畅想过这样一番场景:快下班了,你打开手机,利用服务商提供的服务打开了自家的电饭锅,当你乘车到家时,饭已经做好了,我们可以坐下来享受美食。这并不是幻想,在不久的将来这将是很现实的事情。

    以上写的是相对遥远的事情,下面来看看我们身边发生了什么?

    SaaS(Software as a Service,软件即服务):初次接触SaaS是在今年的十一长假去看望舅舅时,舅舅做了一辈子的会计,他拿出了一本杂志,让我按照杂志上所说的网址上去帮他申请一个账号,并让我教他该怎么用。其实那就是金蝶的一个SaaS产品,只需要在上面申请一个账号,就可以用里面的财务管理功能,数据的存储等等都不用管。只需要每年交上几百元的服务费即可。这件事给我留下了很深的印象,因为的使用成本很低,必定会吸引很多客户。那这些SaaS厂商如何提供多样化的服务呢?因为客户所需要的可不都是财富管理功能。SOA(service-oriented architecture,面向服务架构)很好的解决了这个问题。先来了解一下什么是SOA

    面向服务的体系结构(service-oriented architectureSOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。

    上面的解释过于专业,下面我来将其通俗化:为了满足客户的不同要求,我们写很多很多功能模块,每个模块只完成一个特定的任务,但这个模块是开放的,也就是可以被外部调用的。通过使用接口定义语言定义的接口进行通信。这让我想起了写COM程序时用IDL语言定义接口的情形。这些模块间可以通过这些接口进行自由组合,如果客户有多种需求,可以通过组合不同的模块来满足客户的需求。这似乎又是一个回归,让我想起了Unix下的kissKeep it Simple and Stupid,我们在Linux下写程序的时候,可以在vi/vim里面运行Shell命令,可以在vi/vim里利用gdb调试程序。

    在我们的工作中也了解到,客户的机器上往往运行着多种不同的应用,而且这些应用都是不同厂商用不同的语言实现的,如何实现他们之间的数据共享?如果每一个系统都是独立的,那么在客户的多个应用里面肯定会存在不同程度的重复,操作起来更加复杂,结果是客户花了更多的钱,工作负担依然很大,因为他要熟悉多种应用。假如每个应用都提供对外通讯接口,那么成本就能够降低很多。

    SaaS不一定会成功,但这种模式代表着未来。

    下面来看看我们的厂商:

    微软:微软已经宣称在其下一代操作系统和.NET框架里集成了云计算(SD2.0大会)。

    谷歌:目前我最感兴趣的公司,看看谷歌的产品,或许我们能了解些什么:

                Android:嵌入式操作系统

                Chrome:浏览器

                Docs  在线文档

                Map  地图

          ………………

        谷歌还有很多互联网应用,并且它们都是开源的,而且几乎都是免费的。这里我想对Chrome多说两句,当我们在Chrome里同时打开多个标签时会发现,Chrome采用的是多进程的架构,这就使得它更加安全,更加稳定。据说ChromeJavasrcipt引擎的性能也并其它浏览器高出几十倍,这些将保证Chrome将会给用户以更佳的体验。

    我想起了以前看过的一段新闻,微软嘲笑谷歌有很多产品,但是没有几个能赢利的。其实不然,Android会用户在终端的选择上有了更大的余地,各种在线应用把更多用户的注意力吸引到了互联网上,谷歌只需要在搜索上赢利就足够了。

          文章中若有偏颇之处,请指正!

    展开全文
  • TensorFlow对Android、iOS、树莓派都提供移动端支持。移动端应用原理。移动端、嵌入式设备应用深度学习方式,一模型运行在云端服务器,向服务器发送请求,接收服务器响应;二在本地运行模型,PC训练模型,放到移动端...

    TensorFlow对Android、iOS、树莓派都提供移动端支持。

    移动端应用原理。移动端、嵌入式设备应用深度学习方式,一模型运行在云端服务器,向服务器发送请求,接收服务器响应;二在本地运行模型,PC训练模型,放到移动端预测。向服务端请求数据可行性差,移动端资源稀缺。本地运行实时性更好。加速计算,内存空间和速度优化。精简模型,节省内存空间,加快计算速度。加快框架执行速度,优化模型复杂度和每步计算速度。
    精简模型,用更低权得精度,量化(quantization)、权重剪枝(weight pruning,剪小权重连接,把所有权值连接低于阈值的从网络移除)。加速框架执行,优化矩阵通用乘法(GEMM)运算,影响卷积层(先数据im2col运行,再GEMM运算)和全连接层。im2col,索引图像块重排列为矩阵列。先将大矩阵重叠划分多个子矩阵,每个子矩阵序列化成向量,得到另一个矩阵。

    量化(quantitative)。《How to Quantize Neural Networks with TensorFlow》https://www.tensorflow.org/performance/quantization 。离散化。用比32位浮点数更少空间存储、运行模型,TensorFlow量化实现屏蔽存储、运行细节。神经网络预测,浮点影响速度,量化加快速度,保持较高精度。减小模型文件大小。存储模型用8位整数,加载模型运算转换回32位浮点数。降低预测过程计算资源。神经网络噪声健壮笥强,量化精度损失不会危害整体准确度。训练,反向传播需要计算梯度,不能用低精度格式直接训练。PC训练浮点数模型,转8位,移动端用8位模型预测。
    量化示例。GoogleNet模型转8位模型例子。下载训练好GoogleNet模型,http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz

    bazel build tensorflow/tools/quantization:quantization_graph
    bazel-bin/tensorflow/tools/quantization/quantization_graph \
    --input=/tmp/classify_image_graph_def.pb \
    --output_node_names="softmax" --output=/tmp/quantized_graph.pb \
    --mode=eightbit
    

    生成量化后模型大小只有原来的1/4。执行:

    bazel build tensorflow/examples/label_image:label_image
    bazel-bin/tensorflow/examples/label_image/label_image \
    --image=/tmp/cropped_panda.jpg \
    --graph=/tmp/quantized_graph.pb \
    --labels=/tmp/imagenet_synset_to_human_label_map.txt \
    --input_width=299 \
    --input_height=299 \
    --input_mean=128 \
    --input_std=128 \
    --input_layer="Mul:0" \
    --output_layer="softmax:0"
    

    量化过程实现。预测操作转换成等价8位版本操作实现。原始Relu操作,输入、输出浮点数。量化Relu操作,根据输入浮点数计算最大值、最小值,进入量化(Quantize)操作输入数据转换8位。保证输出层输入数据准确性,需要反量化(Dequantize)操作,权重转回32位精度,保证预测准确性。整个模型前向传播用8位整数支行,最后一层加反量化层,8位转回32位输出层输入。每个量化操作后执行反量化操作。

    量化数据表示。浮点数转8位表示,是压缩问题。权重、经过激活函数处理上层输出,是分布在一个范围内的值。量化过程,找出最大值、最小值,将浮点数线性分布,做线性扩展。

    优化矩阵乘法运算。谷歌开源小型独立低精度通用矩阵乘法(General Matrix to Matrix Multiplication,GEMM)库 gemmlowp。https://github.com/google/gemmlowp

    iOS系统实践。

    环境准备。操作系统Mac OS X,集成开发工具Xcode 7.3以上版本。编译TensorFlow核心静态库。tensorflow/contrib/makefiles/download_depencies.sh 。依赖库下载到tensorflow/contrib/makefile/downloads目录。eigen #C++开源矩阵计算工具。gemmlowp #小型独立低精度通用矩阵乘法(GEMM)库。googletest #谷歌开源C++测试框架。protobuf #谷歌开源数据交换格式协议。re2 #谷歌开源正则表达式库。

    编译演示程度,运行。tensorflow/contrib/makefile/build_all_iso.sh。编译生成静态库,tensorflow/contrib/makefile/gen/lib:ios_ARM64、ios_ARMV7、ios_ARMV7S、ios_I386、ios_X86_64、libtensorflow-core.a。Xcode模拟器或iOS设备运行APP预测示例。TensorFlow iOS示例。https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/ 。3个目录。benchmark目录是预测基准示例。simple目录是图片预测示例。camera目录是视频流实时预测示例。下载Inception V1模型,能识别1000类图片,https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip 。解压模型,复制到benchmark、simple、camera的data目录。运行目录下xcodeproj文件。选择iPhone 7 Plus模拟器,点击运行标志,编译完成点击Run Model按钮。预测结果见Xcode 控制台。

    自定义模型编译、运行。https://github.com/tensorflow/tensorflow/blob/15b1cf025da5c6ac2bcf4d4878ee222fca3aec4a/tensorflow/docs_src/tutorials/image_retraining.md 。下载花卉数据 http://download.tensorflow.org/example_images/flower_photos.tgz 。郁金香(tulips)、玫瑰(roses)、浦公英(dandelion)、向日葵(sunflowers)、雏菊(daisy)5种花卉文件目录,各800张图片。
    训练原始模型。下载预训练Inception V3模型 http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz

    python tensorflow/examples/image_retraining/retrain.py \
    --bottlenectk_dir=/tmp/bottlenecks/ \
    --how_many_training_steps 10 \
    --model_dir=/tmp/inception \
    --output_graph=/tmp/retrained_graph.pb \
    --output_labels=/tmp/retrained_labels.txt \
    --image_dir /tmp/flower_photos
    

    训练完成,/tmp目录有模型文件retrained_graph.pb、标签文件上retrained_labels.txt。“瓶颈”(bottlenecks)文件,描述实际分类最终输出层前一层(倒数第二层)。倒数第二层训练很好,瓶颈值是有意义紧凑图像摘要,包含足够信息使分类选择。第一次训练,retrain.py文件代码先分析所有图片,计算每张图片瓶颈值存储下来。每张图片被使用多次,不必重复计算。

    编译iOS支持模型。https://petewarden.com/2016/09/27/tensorflow-for-mobile-poets/ 。原始模型到iOS模型,先去掉iOS系统不支持操作,优化模型,再将模型量化,权重变8位常数,缩小模型,最后模型内存映射。
    去掉iOS系统不支持操作,优化模型。iOS版本TensorFlow仅支持预测阶段常见没有大外部依赖关系操作。支持操作列表:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/makefile/tf_op_files.txt 。DecodeJpeg不支持,JPEG格式图片解码,依赖libjpeg。从摄像头实时识别花卉种类,直接处理相机图像缓冲区,不存JPEG文件再解码。预训练模型Inception V3 从图片数据集训练,包含DecodeJpeg操作。输入数据直接提供(feed)Decode后Mul操作,绕过Decode操作。优化加速预测,显式批处理规范化(explicit batch normalization)操作合并到卷积权重,减少计算次数。

    bazel build tensorflow/python/tools:optimize_for_inference
    bazel-bin/tensorflow/python/tools/optimize_for_inference \
    --input=/tmp/retrained_graph.pb \
    --output=/tmp/optimized_graph.pb \
    --input_names=Mul \
    --output_names=final_result \
    

    label_image命令预测:

    bazel-bin/tensorflow/examples/label_image/label_image \
    --output_layer=final_result \
    --labels=/tmp/output_labels.txt \
    --image=/tmp/flower_photos/daisy/5547758_eea9edfd54_n.jpg
    --graph=/tmp/output_graph.pb \
    --input_layer=Mul \
    --input_mean=128 \
    --input_std=128 \
    

    量化模型。苹果系统在.ipa包分发应用程度,所有应用程度资源都用zip压缩。模型权重从浮点数转整数(范围0~255),损失准确度,小于1%。

    bazel build tensorflow/tools/quantization:quantization_graph
    bazel-bin/tensorflow/tools/quantization/quantization_graph \
    --input=/tmp/optimized_graph.pb \
    --output=/tmp/rounded_graph.pb \
    --output_node_names=final_result \
    --mode=weights_rounded
    

    内存映射 memory mapping。物理内存映射到进程地址空间内,应用程序直接用输入/输出地址空间,提高读写效率。模型全部一次性加载到内存缓冲区,会对iOS RAM施加过大压力,操作系统会杀死内存占用过多程序。模型权值缓冲区只读,可映射到内存。重新排列模型,权重分部分逐块从主GraphDef加载到内存。

    bazel build tensorflow/contrib/util:convert_graphdef_memmapped_format
    bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format \
    --in_graph=/tmp/rounded_graph.pb \
    --out_graph=/tmp/mmapped_graph.pb
    

    生成iOS工程文件运行。视频流实进预测演示程序例子。https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/ios/camera 。模型文件、标记文件复制到data目录。修改CameraExampleViewController.mm,更改加载模型文件名称、输入图片尺寸、操作节点名字、缩放像素大小。

    #import <AssertMacros.h>
    #import <AssetsLibrary/AssetsLibrary.h>
    #import <CoreImage/CoreImage.h>
    #import <ImageIO/ImageIO.h>
    #import "CameraExampleViewController.h"
    #include <sys/time.h>
    #include "tensorflow_utils.h"
    // If you have your own model, modify this to the file name, and make sure
    // you've added the file to your app resources too.
    static NSString* model_file_name = @"tensorflow_inception_graph";
    static NSString* model_file_type = @"pb";
    // This controls whether we'll be loading a plain GraphDef proto, or a
    // file created by the convert_graphdef_memmapped_format utility that wraps a
    // GraphDef and parameter file that can be mapped into memory from file to
    // reduce overall memory usage.
    const bool model_uses_memory_mapping = false;
    // If you have your own model, point this to the labels file.
    static NSString* labels_file_name = @"imagenet_comp_graph_label_strings";
    static NSString* labels_file_type = @"txt";
    // These dimensions need to match those the model was trained with.
    // 以下尺寸需要和模型训练时相匹配
    const int wanted_input_width =299;// 224;
    const int wanted_input_height = 299;//224;
    const int wanted_input_channels = 3;
    const float input_mean = 128.0f;//117.0f;
    const float input_std = 128.0f;//1.0f;
    const std::string input_layer_name = "Mul";//"input";
    const std::string output_layer_name = "final_result";//"softmax1";
    static void *AVCaptureStillImageIsCapturingStillImageContext =
        &AVCaptureStillImageIsCapturingStillImageContext;
    @interface CameraExampleViewController (InternalMethods)
    - (void)setupAVCapture;
    - (void)teardownAVCapture;
    @end
    @implementation CameraExampleViewController
    - (void)setupAVCapture {
      NSError *error = nil;
      session = [AVCaptureSession new];
      if ([[UIDevice currentDevice] userInterfaceIdiom] ==
          UIUserInterfaceIdiomPhone)
        [session setSessionPreset:AVCaptureSessionPreset640x480];
      else
        [session setSessionPreset:AVCaptureSessionPresetPhoto];
      AVCaptureDevice *device =
          [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
      AVCaptureDeviceInput *deviceInput =
          [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
      assert(error == nil);
      isUsingFrontFacingCamera = NO;
      if ([session canAddInput:deviceInput]) [session addInput:deviceInput];
      stillImageOutput = [AVCaptureStillImageOutput new];
      [stillImageOutput
          addObserver:self
           forKeyPath:@"capturingStillImage"
              options:NSKeyValueObservingOptionNew
              context:(void *)(AVCaptureStillImageIsCapturingStillImageContext)];
      if ([session canAddOutput:stillImageOutput])
        [session addOutput:stillImageOutput];
      videoDataOutput = [AVCaptureVideoDataOutput new];
      NSDictionary *rgbOutputSettings = [NSDictionary
          dictionaryWithObject:[NSNumber numberWithInt:kCMPixelFormat_32BGRA]
                        forKey:(id)kCVPixelBufferPixelFormatTypeKey];
      [videoDataOutput setVideoSettings:rgbOutputSettings];
      [videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
      videoDataOutputQueue =
          dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
      [videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
      if ([session canAddOutput:videoDataOutput])
        [session addOutput:videoDataOutput];
      [[videoDataOutput connectionWithMediaType:AVMediaTypeVideo] setEnabled:YES];
      previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
      [previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
      [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
      CALayer *rootLayer = [previewView layer];
      [rootLayer setMasksToBounds:YES];
      [previewLayer setFrame:[rootLayer bounds]];
      [rootLayer addSublayer:previewLayer];
      [session startRunning];
      if (error) {
        NSString *title = [NSString stringWithFormat:@"Failed with error %d", (int)[error code]];
        UIAlertController *alertController =
            [UIAlertController alertControllerWithTitle:title
                                                message:[error localizedDescription]
                                         preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *dismiss =
            [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:nil];
        [alertController addAction:dismiss];
        [self presentViewController:alertController animated:YES completion:nil];
        [self teardownAVCapture];
      }
    }
    - (void)teardownAVCapture {
      [stillImageOutput removeObserver:self forKeyPath:@"isCapturingStillImage"];
      [previewLayer removeFromSuperlayer];
    }
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
      if (context == AVCaptureStillImageIsCapturingStillImageContext) {
        BOOL isCapturingStillImage =
            [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
        if (isCapturingStillImage) {
          // do flash bulb like animation
          flashView = [[UIView alloc] initWithFrame:[previewView frame]];
          [flashView setBackgroundColor:[UIColor whiteColor]];
          [flashView setAlpha:0.f];
          [[[self view] window] addSubview:flashView];
          [UIView animateWithDuration:.4f
                           animations:^{
                             [flashView setAlpha:1.f];
                           }];
        } else {
          [UIView animateWithDuration:.4f
              animations:^{
                [flashView setAlpha:0.f];
              }
              completion:^(BOOL finished) {
               [flashView removeFromSuperview];
                flashView = nil;
              }];
        }
      }
    }
    - (AVCaptureVideoOrientation)avOrientationForDeviceOrientation:
        (UIDeviceOrientation)deviceOrientation {
      AVCaptureVideoOrientation result =
          (AVCaptureVideoOrientation)(deviceOrientation);
      if (deviceOrientation == UIDeviceOrientationLandscapeLeft)
        result = AVCaptureVideoOrientationLandscapeRight;
      else if (deviceOrientation == UIDeviceOrientationLandscapeRight)
        result = AVCaptureVideoOrientationLandscapeLeft;
      return result;
    }
    - (IBAction)takePicture:(id)sender {
      if ([session isRunning]) {
        [session stopRunning];
        [sender setTitle:@"Continue" forState:UIControlStateNormal];
        flashView = [[UIView alloc] initWithFrame:[previewView frame]];
        [flashView setBackgroundColor:[UIColor whiteColor]];
        [flashView setAlpha:0.f];
        [[[self view] window] addSubview:flashView];
        [UIView animateWithDuration:.2f
            animations:^{
              [flashView setAlpha:1.f];
            }
            completion:^(BOOL finished) {
              [UIView animateWithDuration:.2f
                  animations:^{
                    [flashView setAlpha:0.f];
                  }
                  completion:^(BOOL finished) {
                    [flashView removeFromSuperview];
                    flashView = nil;
                  }];
            }];
      } else {
        [session startRunning];
        [sender setTitle:@"Freeze Frame" forState:UIControlStateNormal];
      }
    }
    + (CGRect)videoPreviewBoxForGravity:(NSString *)gravity
                              frameSize:(CGSize)frameSize
                           apertureSize:(CGSize)apertureSize {
      CGFloat apertureRatio = apertureSize.height / apertureSize.width;
      CGFloat viewRatio = frameSize.width / frameSize.height;
      CGSize size = CGSizeZero;
      if ([gravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) {
        if (viewRatio > apertureRatio) {
          size.width = frameSize.width;
          size.height =
              apertureSize.width * (frameSize.width / apertureSize.height);
        } else {
          size.width =
              apertureSize.height * (frameSize.height / apertureSize.width);
          size.height = frameSize.height;
       }
      } else if ([gravity isEqualToString:AVLayerVideoGravityResizeAspect]) {
        if (viewRatio > apertureRatio) {
          size.width =
              apertureSize.height * (frameSize.height / apertureSize.width);
          size.height = frameSize.height;
        } else {
          size.width = frameSize.width;
          size.height =
              apertureSize.width * (frameSize.width / apertureSize.height);
        }
      } else if ([gravity isEqualToString:AVLayerVideoGravityResize]) {
        size.width = frameSize.width;
        size.height = frameSize.height;
      }
      CGRect videoBox;
      videoBox.size = size;
      if (size.width < frameSize.width)
        videoBox.origin.x = (frameSize.width - size.width) / 2;
      else
        videoBox.origin.x = (size.width - frameSize.width) / 2;
      if (size.height < frameSize.height)
        videoBox.origin.y = (frameSize.height - size.height) / 2;
      else
        videoBox.origin.y = (size.height - frameSize.height) / 2;
      return videoBox;
    }
    - (void)captureOutput:(AVCaptureOutput *)captureOutput
    didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
           fromConnection:(AVCaptureConnection *)connection {
      CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
      CFRetain(pixelBuffer);
      [self runCNNOnFrame:pixelBuffer];
      CFRelease(pixelBuffer);
    }
    - (void)runCNNOnFrame:(CVPixelBufferRef)pixelBuffer {
      assert(pixelBuffer != NULL);
      OSType sourcePixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
      int doReverseChannels;
      if (kCVPixelFormatType_32ARGB == sourcePixelFormat) {
        doReverseChannels = 1;
      } else if (kCVPixelFormatType_32BGRA == sourcePixelFormat) {
        doReverseChannels = 0;
      } else {
        assert(false);  // Unknown source format
      }
      const int sourceRowBytes = (int)CVPixelBufferGetBytesPerRow(pixelBuffer);
      const int image_width = (int)CVPixelBufferGetWidth(pixelBuffer);
      const int fullHeight = (int)CVPixelBufferGetHeight(pixelBuffer);
      CVPixelBufferLockFlags unlockFlags = kNilOptions;
      CVPixelBufferLockBaseAddress(pixelBuffer, unlockFlags);
      unsigned char *sourceBaseAddr =
          (unsigned char *)(CVPixelBufferGetBaseAddress(pixelBuffer));
      int image_height;
      unsigned char *sourceStartAddr;
      if (fullHeight <= image_width) {
        image_height = fullHeight;
        sourceStartAddr = sourceBaseAddr;
      } else {
        image_height = image_width;
        const int marginY = ((fullHeight - image_width) / 2);
        sourceStartAddr = (sourceBaseAddr + (marginY * sourceRowBytes));
      }
      const int image_channels = 4;
      assert(image_channels >= wanted_input_channels);
      tensorflow::Tensor image_tensor(
          tensorflow::DT_FLOAT,
          tensorflow::TensorShape(
              {1, wanted_input_height, wanted_input_width, wanted_input_channels}));
      auto image_tensor_mapped = image_tensor.tensor<float, 4>();
      tensorflow::uint8 *in = sourceStartAddr;
      float *out = image_tensor_mapped.data();
      for (int y = 0; y < wanted_input_height; ++y) {
        float *out_row = out + (y * wanted_input_width * wanted_input_channels);
        for (int x = 0; x < wanted_input_width; ++x) {
          const int in_x = (y * image_width) / wanted_input_width;
          const int in_y = (x * image_height) / wanted_input_height;
          tensorflow::uint8 *in_pixel =
              in + (in_y * image_width * image_channels) + (in_x * image_channels);
          float *out_pixel = out_row + (x * wanted_input_channels);
          for (int c = 0; c < wanted_input_channels; ++c) {
            out_pixel[c] = (in_pixel[c] - input_mean) / input_std;
          }
        }
      }
      CVPixelBufferUnlockBaseAddress(pixelBuffer, unlockFlags);
      if (tf_session.get()) {
        std::vector<tensorflow::Tensor> outputs;
        tensorflow::Status run_status = tf_session->Run(
            {{input_layer_name, image_tensor}}, {output_layer_name}, {}, &outputs);
        if (!run_status.ok()) {
          LOG(ERROR) << "Running model failed:" << run_status;
        } else {
          tensorflow::Tensor *output = &outputs[0];
          auto predictions = output->flat<float>();
          NSMutableDictionary *newValues = [NSMutableDictionary dictionary];
          for (int index = 0; index < predictions.size(); index += 1) {
            const float predictionValue = predictions(index);
            if (predictionValue > 0.05f) {
              std::string label = labels[index % predictions.size()];
              NSString *labelObject = [NSString stringWithUTF8String:label.c_str()];
              NSNumber *valueObject = [NSNumber numberWithFloat:predictionValue];
              [newValues setObject:valueObject forKey:labelObject];
            }
          }
          dispatch_async(dispatch_get_main_queue(), ^(void) {
            [self setPredictionValues:newValues];
          });
        }
      }
      CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    }
    - (void)dealloc {
      [self teardownAVCapture];
    }
    // use front/back camera
    - (IBAction)switchCameras:(id)sender {
      AVCaptureDevicePosition desiredPosition;
      if (isUsingFrontFacingCamera)
        desiredPosition = AVCaptureDevicePositionBack;
      else
        desiredPosition = AVCaptureDevicePositionFront;
      for (AVCaptureDevice *d in
           [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
        if ([d position] == desiredPosition) {
          [[previewLayer session] beginConfiguration];
          AVCaptureDeviceInput *input =
              [AVCaptureDeviceInput deviceInputWithDevice:d error:nil];
          for (AVCaptureInput *oldInput in [[previewLayer session] inputs]) {
            [[previewLayer session] removeInput:oldInput];
          }
          [[previewLayer session] addInput:input];
          [[previewLayer session] commitConfiguration];
          break;
        }
      }
      isUsingFrontFacingCamera = !isUsingFrontFacingCamera;
    }
    - (void)didReceiveMemoryWarning {
      [super didReceiveMemoryWarning];
    }
    - (void)viewDidLoad {
      [super viewDidLoad];
      square = [UIImage imageNamed:@"squarePNG"];
      synth = [[AVSpeechSynthesizer alloc] init];
      labelLayers = [[NSMutableArray alloc] init];
      oldPredictionValues = [[NSMutableDictionary alloc] init];
      tensorflow::Status load_status;
      if (model_uses_memory_mapping) {
        load_status = LoadMemoryMappedModel(
            model_file_name, model_file_type, &tf_session, &tf_memmapped_env);
      } else {
        load_status = LoadModel(model_file_name, model_file_type, &tf_session);
      }
      if (!load_status.ok()) {
        LOG(FATAL) << "Couldn't load model: " << load_status;
      }
      tensorflow::Status labels_status =
          LoadLabels(labels_file_name, labels_file_type, &labels);
      if (!labels_status.ok()) {
        LOG(FATAL) << "Couldn't load labels: " << labels_status;
      }
      [self setupAVCapture];
    }
    - (void)viewDidUnload {
      [super viewDidUnload];
    }
    - (void)viewWillAppear:(BOOL)animated {
      [super viewWillAppear:animated];
    }
    - (void)viewDidAppear:(BOOL)animated {
      [super viewDidAppear:animated];
    }
    - (void)viewWillDisappear:(BOOL)animated {
      [super viewWillDisappear:animated];
    }
    - (void)viewDidDisappear:(BOOL)animated {
      [super viewDidDisappear:animated];
    }
    - (BOOL)shouldAutorotateToInterfaceOrientation:
        (UIInterfaceOrientation)interfaceOrientation {
      return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }
    - (BOOL)prefersStatusBarHidden {
      return YES;
    }
    - (void)setPredictionValues:(NSDictionary *)newValues {
      const float decayValue = 0.75f;
      const float updateValue = 0.25f;
      const float minimumThreshold = 0.01f;
      NSMutableDictionary *decayedPredictionValues =
          [[NSMutableDictionary alloc] init];
      for (NSString *label in oldPredictionValues) {
        NSNumber *oldPredictionValueObject =
            [oldPredictionValues objectForKey:label];
        const float oldPredictionValue = [oldPredictionValueObject floatValue];
        const float decayedPredictionValue = (oldPredictionValue * decayValue);
        if (decayedPredictionValue > minimumThreshold) {
          NSNumber *decayedPredictionValueObject =
              [NSNumber numberWithFloat:decayedPredictionValue];
          [decayedPredictionValues setObject:decayedPredictionValueObject
                                      forKey:label];
        }
      }
      oldPredictionValues = decayedPredictionValues;
      for (NSString *label in newValues) {
        NSNumber *newPredictionValueObject = [newValues objectForKey:label];
        NSNumber *oldPredictionValueObject =
            [oldPredictionValues objectForKey:label];
        if (!oldPredictionValueObject) {
          oldPredictionValueObject = [NSNumber numberWithFloat:0.0f];
        }
        const float newPredictionValue = [newPredictionValueObject floatValue];
        const float oldPredictionValue = [oldPredictionValueObject floatValue];
        const float updatedPredictionValue =
            (oldPredictionValue + (newPredictionValue * updateValue));
        NSNumber *updatedPredictionValueObject =
            [NSNumber numberWithFloat:updatedPredictionValue];
        [oldPredictionValues setObject:updatedPredictionValueObject forKey:label];
      }
      NSArray *candidateLabels = [NSMutableArray array];
      for (NSString *label in oldPredictionValues) {
        NSNumber *oldPredictionValueObject =
            [oldPredictionValues objectForKey:label];
        const float oldPredictionValue = [oldPredictionValueObject floatValue];
        if (oldPredictionValue > 0.05f) {
          NSDictionary *entry = @{
            @"label" : label,
            @"value" : oldPredictionValueObject
          };
          candidateLabels = [candidateLabels arrayByAddingObject:entry];
        }
      }
      NSSortDescriptor *sort =
          [NSSortDescriptor sortDescriptorWithKey:@"value" ascending:NO];
      NSArray *sortedLabels = [candidateLabels
          sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
      const float leftMargin = 10.0f;
      const float topMargin = 10.0f;
      const float valueWidth = 48.0f;
      const float valueHeight = 26.0f;
      const float labelWidth = 246.0f;
      const float labelHeight = 26.0f;
      const float labelMarginX = 5.0f;
      const float labelMarginY = 5.0f;
      [self removeAllLabelLayers];
      int labelCount = 0;
      for (NSDictionary *entry in sortedLabels) {
        NSString *label = [entry objectForKey:@"label"];
        NSNumber *valueObject = [entry objectForKey:@"value"];
        const float value = [valueObject floatValue];
        const float originY =
            (topMargin + ((labelHeight + labelMarginY) * labelCount));
        const int valuePercentage = (int)roundf(value * 100.0f);
        const float valueOriginX = leftMargin;
        NSString *valueText = [NSString stringWithFormat:@"%d%%", valuePercentage];
        [self addLabelLayerWithText:valueText
                            originX:valueOriginX
                            originY:originY
                              width:valueWidth
                             height:valueHeight
                          alignment:kCAAlignmentRight];
        const float labelOriginX = (leftMargin + valueWidth + labelMarginX);
        [self addLabelLayerWithText:[label capitalizedString]
                            originX:labelOriginX
                            originY:originY
                              width:labelWidth
                             height:labelHeight
                          alignment:kCAAlignmentLeft];
        if ((labelCount == 0) && (value > 0.5f)) {
          [self speak:[label capitalizedString]];
        }
        labelCount += 1;
        if (labelCount > 4) {
          break;
        }
      }
    }
    - (void)removeAllLabelLayers {
      for (CATextLayer *layer in labelLayers) {
        [layer removeFromSuperlayer];
      }
      [labelLayers removeAllObjects];
    }
    - (void)addLabelLayerWithText:(NSString *)text
                          originX:(float)originX
                          originY:(float)originY
                            width:(float)width
                           height:(float)height
                        alignment:(NSString *)alignment {
      CFTypeRef font = (CFTypeRef) @"Menlo-Regular";
      const float fontSize = 20.0f;
      const float marginSizeX = 5.0f;
      const float marginSizeY = 2.0f;
      const CGRect backgroundBounds = CGRectMake(originX, originY, width, height);
      const CGRect textBounds =
          CGRectMake((originX + marginSizeX), (originY + marginSizeY),
                     (width - (marginSizeX * 2)), (height - (marginSizeY * 2)));
      CATextLayer *background = [CATextLayer layer];
      [background setBackgroundColor:[UIColor blackColor].CGColor];
      [background setOpacity:0.5f];
      [background setFrame:backgroundBounds];
      background.cornerRadius = 5.0f;
      [[self.view layer] addSublayer:background];
      [labelLayers addObject:background];
      CATextLayer *layer = [CATextLayer layer];
      [layer setForegroundColor:[UIColor whiteColor].CGColor];
      [layer setFrame:textBounds];
      [layer setAlignmentMode:alignment];
      [layer setWrapped:YES];
      [layer setFont:font];
      [layer setFontSize:fontSize];
      layer.contentsScale = [[UIScreen mainScreen] scale];
      [layer setString:text];
      [[self.view layer] addSublayer:layer];
      [labelLayers addObject:layer];
    }
    - (void)setPredictionText:(NSString *)text withDuration:(float)duration {
      if (duration > 0.0) {
        CABasicAnimation *colorAnimation =
            [CABasicAnimation animationWithKeyPath:@"foregroundColor"];
        colorAnimation.duration = duration;
        colorAnimation.fillMode = kCAFillModeForwards;
        colorAnimation.removedOnCompletion = NO;
        colorAnimation.fromValue = (id)[UIColor darkGrayColor].CGColor;
        colorAnimation.toValue = (id)[UIColor whiteColor].CGColor;
        colorAnimation.timingFunction =
            [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        [self.predictionTextLayer addAnimation:colorAnimation
                                        forKey:@"colorAnimation"];
      } else {
        self.predictionTextLayer.foregroundColor = [UIColor whiteColor].CGColor;
      }
      [self.predictionTextLayer removeFromSuperlayer];
      [[self.view layer] addSublayer:self.predictionTextLayer];
      [self.predictionTextLayer setString:text];
    }
    - (void)speak:(NSString *)words {
      if ([synth isSpeaking]) {
        return;
      }
      AVSpeechUtterance *utterance =
          [AVSpeechUtterance speechUtteranceWithString:words];
      utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"];
      utterance.rate = 0.75 * AVSpeechUtteranceDefaultSpeechRate;
      [synth speakUtterance:utterance];
    }
    @end
    

    连上iPhone手机,双击tensorflow/contrib/ios_examples/camera/camera_example.xcodeproj编译运行。手机安装好APP,打开APP,找到玫瑰花识别。训练迭代次数10000次后,识别率99%以上。模拟器打包,生成打包工程文件位于/Users/libinggen/Library/Developer/Xcode/DeriveData/camera_example-dhfdsdfesfmrwtfb1fpfkfjsdfhdskf/Build/Products/Debug-iphoneos。打开CameraExample.app,有可执行文件CameraExample、资源文件模型文件mmapped_graph.pb、标记文件retrained_labels.txt。

    Android系统实践。

    环境准备。MacBook Pro。Oracle官网下载JDK1.8版本。http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 。jdk-8u111-macosx-x64.dmg。双击安装。设置Java环境变量:

    JAVA_HOME='/usr/libexec/java_home'
    export JAVA_HOME
    

    搭建Android SDK环境。Android官网下载Android SDK,https://developer.android.com 。25.0.2版本。android-sdk_r25.0.2-macosx.zip。解压到~/Library/Android/sdk目录。build-tools、extras、patcher、platform-tools #各版本SDK 根据API Level划分SDK版本、platforms、sources、system-images、temp #临时文件夹 在SDK更新安装时用到、tools #各版本通用SDK工具 有adb、aapt、aidl、dx文件。
    搭建Android NDK环境。Android官网下载Android NDK Mac OS X版本,https://developer.android.com/ndk/downloads/index.html 。android-ndk-r13b-darwin-x86_64.zip文件。解压,CHANGELOG.md、build、ndk-build、ndk-depends、ndk-gdb、ndk-stack、ndk-which、platforms、prebuilt、python-packages、shader-tools、simpleperf、source.properties、sources、toolchains。搭建Bazel。brew安装bazel:

    brew install bazel
    

    更新bazel:

    brew upgrade bazel
    

    编译演示程序运行。修改tensorflow-1.1.0根目录WORKSPACE文件。android_sdk_repository、android_ndk_repository配置改为用户自己安装目录、版本。

    android_sdk_repository(
        name = "androidsdk",
        api_level = 25,
        build_tools_version = "25.0.2",
        # Replace with path to Android SDK on your system
        path = "~/Library/Android/sdk"
    )
    android_ndk_repository(
        name = "androidndk",
        api_level = 23,
        path = "~/Downloads/android-ndk-r13b"
    )
    

    在根目录用bazel构建:

    bazel build // tensorflow/examples/android:tensorflow_demo
    

    编译成功,默认在tensorflow-1.1.0/bazel-bin/tensorflow/examples/android目录生成TensorFlow演示程序。
    运行。生成apk文件传输到手机,手机摄像头看效果。Android 6.0.1。开启“开发者模式”。手机用数据线与计算机相连,进入SDK所在目录,进入platform-tools文件夹,找到adb命令,执行:

    ./adb install tensorflow-0.12/bazel-bin/tensorflow/examples/android/tensorflow_demo.apk
    

    tensorflow_demo.apk自动安装到手机。打开TF Detec App。App 调起手机摄像头,摄像头返回数据流实时监测。

    自定义模型编译运行。训练原始模型、编译Android系统支持模型、生成Android apk文件运行。
    训练原始模型、编译Android系统支持模型。用项目根目录tensorflow/python/tools/optimize_for_inference.py、tensorflow/tools/quantization/quantize_graph.py、tensorflow/contrib/util/convert_graphdef_memmapped_format.cc对模型优化。将第一步生成原始模型文件retrained_graph.pb、标记文件retrained_labels.txt放在tensorflow/examples/android/assets目录。修改tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowImageClassifier.java要加载模型文件名称,输入图片尺寸、操作节点名字、缩放像素大小。

    package org.tensorflow.demo;
    import android.content.res.AssetManager;
    import android.graphics.Bitmap;
    import android.os.Trace;
    import android.util.Log;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.PriorityQueue;
    import java.util.Vector;
    import org.tensorflow.Operation;
    import org.tensorflow.contrib.android.TensorFlowInferenceInterface;
    /** A classifier specialized to label images using TensorFlow. */
    public class TensorFlowImageClassifier implements Classifier {
      private static final String TAG = "TensorFlowImageClassifier";
      // Only return this many results with at least this confidence.
      private static final int MAX_RESULTS = 3;
      private static final float THRESHOLD = 0.1f;
      // Config values.
      private String inputName;
      private String outputName;
      private int inputSize;
      private int imageMean;
      private float imageStd;
      // Pre-allocated buffers.
      private Vector<String> labels = new Vector<String>();
      private int[] intValues;
      private float[] floatValues;
      private float[] outputs;
      private String[] outputNames;
      private boolean logStats = false;
      private TensorFlowInferenceInterface inferenceInterface;
      private TensorFlowImageClassifier() {}
      /**
       * Initializes a native TensorFlow session for classifying images.
       *
       * @param assetManager The asset manager to be used to load assets.
       * @param modelFilename The filepath of the model GraphDef protocol buffer.
       * @param labelFilename The filepath of label file for classes.
       * @param inputSize The input size. A square image of inputSize x inputSize is assumed.
       * @param imageMean The assumed mean of the image values.
       * @param imageStd The assumed std of the image values.
       * @param inputName The label of the image input node.
       * @param outputName The label of the output node.
       * @throws IOException
       */
      public static Classifier create(
          AssetManager assetManager,
          String modelFilename,
          String labelFilename,
          int inputSize,
          int imageMean,
          float imageStd,
          String inputName,
          String outputName) {
        TensorFlowImageClassifier c = new TensorFlowImageClassifier();
        c.inputName = inputName;
        c.outputName = outputName;
        // Read the label names into memory.
        // TODO(andrewharp): make this handle non-assets.
        String actualFilename = labelFilename.split("file:///android_asset/")[1];
        Log.i(TAG, "Reading labels from: " + actualFilename);
        BufferedReader br = null;
        try {
          br = new BufferedReader(new InputStreamReader(assetManager.open(actualFilename)));
          String line;
          while ((line = br.readLine()) != null) {
            c.labels.add(line);
          }
          br.close();
        } catch (IOException e) {
          throw new RuntimeException("Problem reading label file!" , e);
        }
        c.inferenceInterface = new TensorFlowInferenceInterface(assetManager, modelFilename);
        // The shape of the output is [N, NUM_CLASSES], where N is the batch size.
        final Operation operation = c.inferenceInterface.graphOperation(outputName);
        final int numClasses = (int) operation.output(0).shape().size(1);
        Log.i(TAG, "Read " + c.labels.size() + " labels, output layer size is " + numClasses);
        // Ideally, inputSize could have been retrieved from the shape of the input operation.  Alas,
        // the placeholder node for input in the graphdef typically used does not specify a shape, so it
        // must be passed in as a parameter.
        c.inputSize = inputSize;
        c.imageMean = imageMean;
        c.imageStd = imageStd;
        // Pre-allocate buffers.
        c.outputNames = new String[] {outputName};
        c.intValues = new int[inputSize * inputSize];
        c.floatValues = new float[inputSize * inputSize * 3];
        c.outputs = new float[numClasses];
        return c;
      }
      @Override
      public List<Recognition> recognizeImage(final Bitmap bitmap) {
        // Log this method so that it can be analyzed with systrace.
        Trace.beginSection("recognizeImage");
        Trace.beginSection("preprocessBitmap");
        // Preprocess the image data from 0-255 int to normalized float based
        // on the provided parameters.
        bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for (int i = 0; i < intValues.length; ++i) {
          final int val = intValues[i];
          floatValues[i * 3 + 0] = (((val >> 16) & 0xFF) - imageMean) / imageStd;
          floatValues[i * 3 + 1] = (((val >> 8) & 0xFF) - imageMean) / imageStd;
          floatValues[i * 3 + 2] = ((val & 0xFF) - imageMean) / imageStd;
        }
        Trace.endSection();
        // Copy the input data into TensorFlow.
        Trace.beginSection("feed");
        inferenceInterface.feed(inputName, floatValues, 1, inputSize, inputSize, 3);
        Trace.endSection();
        // Run the inference call.
        Trace.beginSection("run");
        inferenceInterface.run(outputNames, logStats);
        Trace.endSection();
        // Copy the output Tensor back into the output array.
        Trace.beginSection("fetch");
        inferenceInterface.fetch(outputName, outputs);
        Trace.endSection();
        // Find the best classifications.
        PriorityQueue<Recognition> pq =
            new PriorityQueue<Recognition>(
                3,
                new Comparator<Recognition>() {
                  @Override
                  public int compare(Recognition lhs, Recognition rhs) {
                    // Intentionally reversed to put high confidence at the head of the queue.
                    return Float.compare(rhs.getConfidence(), lhs.getConfidence());
                  }
                });
        for (int i = 0; i < outputs.length; ++i) {
          if (outputs[i] > THRESHOLD) {
            pq.add(
                new Recognition(
                    "" + i, labels.size() > i ? labels.get(i) : "unknown", outputs[i], null));
          }
        }
        final ArrayList<Recognition> recognitions = new ArrayList<Recognition>();
        int recognitionsSize = Math.min(pq.size(), MAX_RESULTS);
        for (int i = 0; i < recognitionsSize; ++i) {
          recognitions.add(pq.poll());
        }
        Trace.endSection(); // "recognizeImage"
        return recognitions;
      }
      @Override
      public void enableStatLogging(boolean logStats) {
        this.logStats = logStats;
      }
      @Override
      public String getStatString() {
        return inferenceInterface.getStatString();
      }
      @Override
      public void close() {
        inferenceInterface.close();
      }
    }
    

    重新编译apk,连接Android手机,安装apk:

    bazel buld //tensorflow/examples/android:tensorflow_demo
    adb install -r -g bazel-bin/tensorflow/examples/android/tensorflow_demo.apk
    

    树莓派实践。

    Tensorflow可以在树莓派(Raspberry Pi)运行。树莓派,只有信用卡大小微型电脑,系统基于Linux,有音频、视频功能。应用,输入1万张自己的面部图片,在树莓派训练人脸识别模型,教会它认识你,你进入家门后,帮你开灯、播放音乐各种功能。树莓派编译方法和直接在Linux环境上用相似。

    参考资料:
    《TensorFlow技术解析与实战》

    欢迎推荐上海机器学习工作机会,我的微信:qingxingfengzi

    展开全文
  • Aid Learning FrameWork是一个在Android手机上运行的带图形界面的Linux系统,用于AI编程。这意味着当它安装时,你的Android手机拥有一个可以在其中运行AI程序的Linux系统。现在我们有力地支持Caffe,Tensorflow,...
  • TensorFlow对Android、iOS、树莓派都提供移动端支持。 移动端应用原理。移动端、嵌入式设备应用深度学习方式,一模型运行在云端服务器,向服务器发送请求,接收服务器响应;二在本地运行模型,PC训练模型,放到...
  • 微信公众号:voidar一、简介云识别(图像识别)是将识别数据(识别图和显示资源)存放在云服务器上,在云端运行识别算法,实现进行精准识别及跟踪的AR效果。二、阅读指南本教程主要介绍如何在Unity
  • 同时,一台运行Android 5.0系统的索尼手机的性能测试截图也被曝光,加大了传闻的真实性。随着谷歌I/O 2013大会的日益临近,关于Android 5.0系统的传言也愈演愈烈,下面我们便将其汇总呈现给大家作为参考。 发布...
  • 智慧安防在云端

    2011-05-30 14:15:00
      <br /> 老子《道德经》的这句话涵盖了世间万物的运行规律。  <br /> 从上世纪四十年代第一台计算机被人类发明出来,到上世纪七十年代互联网的出现,再到现在云计算的出现,恰恰印证了计算节点由单点...
  • “在移动互联网时代,没有一件可以让别人刮目相看的浏览器工具,你都不好意思说自己是个IT人”,当这句微博上的“名言”成为智能手机浏览器形容句的时候,关于智能手机浏览器如何做到创新、用户体验等方面的话题,...
  • 一、背景 1.1 方案背景 随着互联网基础设施建设的不断完善和发展,带宽的不断提速,尤其是光纤入户,4G/5G/NB-IoT各种技术的大规模商用,视频在各行各业越来越受到重视,无论是传统的视频媒体转向移动互联网的短...
  • 本文仅是博主为记录和加深印象所整理,有兴趣请阅读原文。 原文地址: ...移动端运行神经网络成了明显的趋势,Google的MobileNet框架可以完成这样的功能。MobileNet非常非常
  • 阿里云OS(YunOS)是阿里巴巴集团的智能手机操作系统,依托于阿里巴巴集团电子商务领域积累的经验和强大的云计算平台,基于LINUX开发。 魅族4阿里yun OS版已上市。[1] 1简介 阿 里云OS是融云数据存储、云计算服务和...
  • 当移动浪潮来袭,不论是传统 PC 网站/应用,还是新兴的移动互联网,都一并蜂拥的走进用户的手机。提供一个便于手机浏览的 Web 页面,再造一个功能丰富的移动 App 成了每个产品的标配。提供移动 Web 页面,可以使得...
  • 阿里妹导读:当前移动互联网业务研发运维模式,云与多端互相割裂,有些全栈的探索缺乏成功案例,行业对云端一体化研发这块仍是空白,我们要思考:如何能实现 1 个研发支撑云 + android + iOS 三端的业务快速落地?...
1 2 3 4 5 ... 20
收藏数 406
精华内容 162