amabri的spark怎么提交spark任务提交过程

你的位置: >
> spark streaming 优化方案
hadoop 2.7
spark 1.4.1
两个优化代码序列化方式优化为:Kryo
conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)
默认是Java序列化方式太渣。
修改executor的GC方式为CMS
默认的 Parallel GC 不太适合Spark Streaming
Custom spark-defaults 添加一项:
spark.executor.extraJavaOptions=-XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
修改spark 的日志级别为warn
修改log4j配置文件warn
放置日志文件过大,影响视听。
RDD存储策略
使用StorageLevel.MEMORY_AND_DISK_SER_2或者StorageLevel.MEMORY_ONLY_SER_2
生产环境选择MEMORY_ONLY_SER_2(两节点down)、MEMORY_AND_DISK_SER(节点down后,没来得及写磁盘)都不合适。
注意:如果是缓存策略是XXX_AND_DISK
这里的DISK的意思是说如果内存不够用了就将一些数据dump到磁盘上去。
这里需要注意的是如果是spark on yarn运行,
那么需要注意yarn再启动executor时候回在jvm参数中加 -XX:OnOutOfMemoryError=kill %p
这样会导致,如果你的executor在打到这个OOM的时候,spark 会将数据dump到磁盘;但是yarn JVM会认为你OOM 执行kill
那么就会看到熟悉的java.lang.Exception: Could not compute split, block input-0-0 not found
也就是你的AND_DISK 在yarn上是没用的,除非你手动去掉-XX:OnOutOfMemoryError=kill %p 这个配置。
手动修改spark yarn 历史数据收集配置 for Amabri HDP2.3
解决了driver OOM问题
vi /etc/spark/2.3.2.0-2950/0/spark-defaults.conf
#注释掉下面四个配置项:
#spark.history.provider org.apache.spark.deploy.yarn.history.YarnHistoryProvider
#spark.history.ui.port 18080
#spark.yarn.historyServer.address bigdata3:18080
#spark.yarn.services org.apache.spark.deploy.yarn.history.YarnHistoryService
shuffle 代码优化(大块头还未进行)。参考:/frankbadpot/p/3969119.html
———&#-2-1–添加—–
7. –conf spark.yarn.executor.memoryOverhead 600
8. spark.executor.extraJavaOptions = -XX:MaxPermSize=256m
9. 优化yarn excutor的log4j文件级别为WARN
#在spark-submit命令中使用
spark-submit --files /usr/hdp/current/spark-client/conf/log4j.properties
#添加下面配置,只有添加上面--files 的配置下面的配置才能起作用。
#vi /usr/hdp/current/spark-client/conf/spark-defaults.conf
spark.executor.extraJavaOptions = -Dlog4j.configuration=log4j.properties
优化点1和2 可能对解决excutor内存一直增长问题有关。
转载请注明: &
与本文相关的文章&p&转载请注明:&a href=&/easyml& class=&internal&&炼丹实验室&/a&&br&&/p&&p&之前曾经写过一篇文章,讲了一些深度学习训练的技巧,其中包含了部分调参心得:&a href=&/p/& class=&internal&&深度学习训练心得&/a&。不过由于一般深度学习实验,相比普通机器学习任务,时间较长,因此调参技巧就显得尤为重要。同时个人实践中,又有一些新的调参心得,因此这里单独写一篇文章,谈一下自己对深度学习调参的理解,大家如果有其他技巧,也欢迎多多交流。&/p&&h1&好的实验环境是成功的一半&/h1&&p&由于深度学习实验超参众多,代码风格良好的实验环境,可以让你的人工或者自动调参更加省力,有以下几点可能需要注意:&/p&&ul&&li&将各个参数的设置部分集中在一起。如果参数的设置分布在代码的各个地方,那么修改的过程想必会非常痛苦。&/li&&li&可以输出模型的损失函数值以及训练集和验证集上的准确率。&/li&&li&可以考虑设计一个子程序,可以根据给定的参数,启动训练并监控和周期性保存评估结果。再由一个主程序,分配参数以及并行启动一系列子程序。&/li&&/ul&&h1&画图&/h1&&p&画图是一个很好的习惯,一般是训练数据遍历一轮以后,就输出一下训练集和验证集准确率。同时画到一张图上。这样训练一段时间以后,如果模型一直没有收敛,那么就可以停止训练,尝试其他参数了,以节省时间。 &br&如果训练到最后,训练集,测试集准确率都很低,那么说明模型有可能欠拟合。那么后续调节参数方向,就是增强模型的拟合能力。例如增加网络层数,增加节点数,减少dropout值,减少L2正则值等等。 &br&如果训练集准确率较高,测试集准确率比较低,那么模型有可能过拟合,这个时候就需要向提高模型泛化能力的方向,调节参数。&/p&&h1&从粗到细分阶段调参&/h1&&p&实践中,一般先进行初步范围搜索,然后根据好结果出现的地方,再缩小范围进行更精细的搜索。&/p&&ol&&li&建议先参考相关论文,以论文中给出的参数作为初始参数。至少论文中的参数,是个不差的结果。&/li&&li&如果找不到参考,那么只能自己尝试了。可以先从比较重要,对实验结果影响比较大的参数开始,同时固定其他参数,得到一个差不多的结果以后,在这个结果的基础上,再调其他参数。例如学习率一般就比正则值,dropout值重要的话,学习率设置的不合适,不仅结果可能变差,模型甚至会无法收敛。&/li&&li&如果实在找不到一组参数,可以让模型收敛。那么就需要检查,是不是其他地方出了问题,例如模型实现,数据等等。可以参考我写的&a href=&/p/& class=&internal&&深度学习网络调试技巧&/a&&/li&&/ol&&h1&提高速度&/h1&&p&调参只是为了寻找合适的参数,而不是产出最终模型。一般在小数据集上合适的参数,在大数据集上效果也不会太差。因此可以尝试对数据进行精简,以提高速度,在有限的时间内可以尝试更多参数。&/p&&ul&&li&对训练数据进行采样。例如原来100W条数据,先采样成1W,进行实验看看。&/li&&li&减少训练类别。例如手写数字识别任务,原来是10个类别,那么我们可以先在2个类别上训练,看看结果如何。&/li&&/ul&&h1&超参数范围&/h1&&p&建议优先在对数尺度上进行超参数搜索。比较典型的是学习率和正则化项,我们可以从诸如0.001 0.01 0.1 1 10,以10为阶数进行尝试。因为他们对训练的影响是相乘的效果。不过有些参数,还是建议在原始尺度上进行搜索,例如dropout值: 0.3 0.5 0.7)。&/p&&h1&经验参数&/h1&&p&这里给出一些参数的经验值,避免大家调参的时候,毫无头绪。&/p&&ul&&li&learning rate: 1 0.1 0.01 0.001, 一般从1开始尝试。很少见learning rate大于10的。学习率一般要随着训练进行衰减。衰减系数一般是0.5。 衰减时机,可以是验证集准确率不再上升时,或固定训练多少个周期以后。 &br&不过更建议使用自适应梯度的办法,例如adam,adadelta,rmsprop等,这些一般使用相关论文提供的默认值即可,可以避免再费劲调节学习率。对RNN来说,有个经验,如果RNN要处理的序列比较长,或者RNN层数比较多,那么learning rate一般小一些比较好,否则有可能出现结果不收敛,甚至Nan等问题。&/li&&li&网络层数: 先从1层开始。&/li&&li&每层结点数: 16 32 128,超过1000的情况比较少见。超过1W的从来没有见过。&/li&&li&batch size: 128上下开始。batch size值增加,的确能提高训练速度。但是有可能收敛结果变差。如果显存大小允许,可以考虑从一个比较大的值开始尝试。因为batch size太大,一般不会对结果有太大的影响,而batch size太小的话,结果有可能很差。&/li&&li&clip c(梯度裁剪): 限制最大梯度,其实是value = sqrt(w1^2+w2^2….),如果value超过了阈值,就算一个衰减系系数,让value的值等于阈值: 5,10,15&/li&&li&dropout: 0.5&/li&&li&L2正则:1.0,超过10的很少见。&/li&&li&词向量embedding大小:128,256&/li&&li&正负样本比例: 这个是非常忽视,但是在很多分类问题上,又非常重要的参数。很多人往往习惯使用训练数据中默认的正负类别比例,当训练数据非常不平衡的时候,模型很有可能会偏向数目较大的类别,从而影响最终训练结果。除了尝试训练数据默认的正负类别比例之外,建议对数目较小的样本做过采样,例如进行复制。提高他们的比例,看看效果如何,这个对多分类问题同样适用。 &br&在使用mini-batch方法进行训练的时候,尽量让一个batch内,各类别的比例平衡,这个在图像识别等多分类任务上非常重要。&/li&&/ul&&h1&自动调参&/h1&&p&人工一直盯着实验,毕竟太累。自动调参当前也有不少研究。下面介绍几种比较实用的办法:&/p&&ul&&li&Gird Search. 这个是最常见的。具体说,就是每种参数确定好几个要尝试的值,然后像一个网格一样,把所有参数值的组合遍历一下。优点是实现简单暴力,如果能全部遍历的话,结果比较可靠。缺点是太费时间了,特别像神经网络,一般尝试不了太多的参数组合。&/li&&li&Random Search。Bengio在&a href=&/?target=http%3A//www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Random Search for Hyper-Parameter Optimization&i class=&icon-external&&&/i&&/a&中指出,Random Search比Gird Search更有效。实际操作的时候,一般也是先用Gird Search的方法,得到所有候选参数,然后每次从中随机选择进行训练。&/li&&li&Bayesian Optimization. 贝叶斯优化,考虑到了不同参数对应的实验结果值,因此更节省时间。和网络搜索相比简直就是老牛和跑车的区别。具体原理可以参考这个论文: &a href=&/?target=http%3A//papers.nips.cc/paper/4522-practical-bayesian-optimization-of-machine-learning-algorithms.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Practical Bayesian Optimization of Machine Learning Algorithms&i class=&icon-external&&&/i&&/a& ,这里同时推荐两个实现了贝叶斯调参的Python库,可以上手即用:&ul&&li&&a href=&/?target=https%3A///jaberg/hyperopt& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&jaberg/hyperopt&i class=&icon-external&&&/i&&/a&, 比较简单。&/li&&li&&a href=&/?target=https%3A///fmfn/BayesianOptimization& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&fmfn/BayesianOptimization&i class=&icon-external&&&/i&&/a&, 比较复杂,支持并行调参。&/li&&/ul&&/li&&/ul&&h1&总结&/h1&&ul&&li&合理性检查,确定模型,数据和其他地方没有问题。&/li&&li&训练时跟踪损失函数值,训练集和验证集准确率。&/li&&li&使用Random Search来搜索最优超参数,分阶段从粗(较大超参数范围训练较少周期)到细(较小超参数范围训练较长周期)进行搜索。&/li&&/ul&&h1&参考资料&/h1&&p&这里列了一些参数资料,大家有时间,可以进一步阅读。 &br&&a href=&/?target=https%3A//arxiv.org/abs/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Practical recommendations for gradient-based training of deep architectures by Yoshua Bengio (2012)&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A///exdb/publis/pdf/lecun-98b.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Efficient BackProp, by Yann LeCun, Léon Bottou, Genevieve Orr and Klaus-Robert Müller&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A///computer/theoretical%2Bcomputer%2Bscience/book/978-3-642-35288-1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Neural Networks: Tricks of the Trade, edited by Grégoire Montavon, Geneviève Orr, and Klaus-Robert Müller.&i class=&icon-external&&&/i&&/a&&/p&
转载请注明: 之前曾经写过一篇文章,讲了一些深度学习训练的技巧,其中包含了部分调参心得:。不过由于一般深度学习实验,相比普通机器学习任务,时间较长,因此调参技巧就显得尤为重要。同时个人实践中,又有一些新的调参心得…
&img src=&/v2-ab904178abfe2d0fa7c0584_b.png& data-rawwidth=&1702& data-rawheight=&642& class=&origin_image zh-lightbox-thumb& width=&1702& data-original=&/v2-ab904178abfe2d0fa7c0584_r.png&&&p&近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文题目便是文本分类问题,趁此机会总结下文本分类领域特别是应用深度学习解决文本分类的相关的思路、做法和部分实践的经验。&/p&&p&&b&业务问题描述&/b&:&/p&&p&淘宝商品的一个典型的例子见下图,图中商品的标题是“&u&夏装雪纺条纹短袖t恤女春半袖衣服夏天中长款大码胖mm显瘦上衣夏&/u&”。淘宝网后台是通过树形的多层的类目体系管理商品的,覆盖叶子类目数量达上万个,商品量也是10亿量级,我们是任务是根据商品标题预测其所在叶子类目,示例中商品归属的类目为“女装/女士精品&&蕾丝衫/雪纺衫”。很显然,这是一个非常典型的短文本多分类问题。接下来分别会介绍下文本分类传统和深度学习的做法,最后简单梳理下实践的经验。&/p&&br&&img src=&/v2-e4ac260cc61de_b.png& data-rawwidth=&1002& data-rawheight=&527& class=&origin_image zh-lightbox-thumb& width=&1002& data-original=&/v2-e4ac260cc61de_r.png&&&br&&h2&&b&一、传统文本分类方法&/b&&/h2&&p&文本分类问题算是自然语言处理领域中一个非常经典的问题了,相关研究最早可以追溯到上世纪50年代,当时是通过专家规则(Pattern)进行分类,甚至在80年代初一度发展到利用知识工程建立专家系统,这样做的好处是短平快的解决top问题,但显然天花板非常低,不仅费时费力,覆盖的范围和准确率都非常有限。&/p&&br&&p&后来伴随着统计学习方法的发展,特别是90年代后互联网在线文本数量增长和机器学习学科的兴起,逐渐形成了一套解决大规模文本分类问题的经典玩法,这个阶段的主要套路是人工特征工程+浅层分类模型。训练文本分类器过程见下图:&/p&&img src=&/v2-e8ccfb6b7e58_b.jpg& data-rawwidth=&1991& data-rawheight=&469& class=&origin_image zh-lightbox-thumb& width=&1991& data-original=&/v2-e8ccfb6b7e58_r.jpg&&&p&整个文本分类问题就拆分成了特征工程和分类器两部分,玩机器学习的同学对此自然再熟悉不过了&/p&&p&&b&1.1 特征工程&/b&&/p&&br&&p&特征工程在机器学习中往往是最耗时耗力的,但却极其的重要。抽象来讲,机器学习问题是把数据转换成信息再提炼到知识的过程,特征是“数据--&信息”的过程,决定了结果的上限,而分类器是“信息--&知识”的过程,则是去逼近这个上限。然而特征工程不同于分类器模型,不具备很强的通用性,往往需要结合对特征任务的理解。&/p&&p&文本分类问题所在的自然语言领域自然也有其特有的特征处理逻辑,传统分本分类任务大部分工作也在此处。文本特征工程分位文本预处理、特征提取、文本表示三个部分,最终目的是把文本转换成计算机可理解的格式,并封装足够用于分类的信息,即很强的特征表达能力。&/p&&p&&b&1)文本预处理&/b&&/p&&p&文本预处理过程是在文本中提取关键词表示文本的过程,中文文本处理中主要包括文本分词和去停用词两个阶段。之所以进行分词,是因为很多研究表明特征粒度为词粒度远好于字粒度,其实很好理解,因为大部分分类算法不考虑词序信息,基于字粒度显然损失了过多“n-gram”信息。&/p&&p&具体到中文分词,不同于英文有天然的空格间隔,需要设计复杂的分词算法。传统算法主要有基于字符串匹配的正向/逆向/双向最大匹配;基于理解的句法和语义分析消歧;基于统计的互信息/CRF方法。近年来随着深度学习的应用,WordEmbedding + Bi-LSTM+CRF方法逐渐成为主流,本文重点在文本分类,就不展开了。而停止词是文本中一些高频的代词连词介词等对文本分类无意义的词,通常维护一个停用词表,特征提取过程中删除停用表中出现的词,本质上属于特征选择的一部分。&/p&&p&经过文本分词和去停止词之后淘宝商品示例标题变成了下图“ / ”分割的一个个关键词的形式:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&夏装 / 雪纺 / 条纹 / 短袖 / t恤 / 女 / 春 / 半袖 / 衣服 / 夏天 / 中长款 / 大码 / 胖mm / 显瘦 / 上衣 / 夏
&/code&&/pre&&/div&&p&&b&2)文本表示和特征提取&/b&&/p&&p&&b&文本表示:&/b&&/p&&p&文本表示的目的是把文本预处理后的转换成计算机可理解的方式,是决定文本分类质量最重要的部分。传统做法常用词袋模型(BOW, Bag Of Words)或向量空间模型(Vector Space Model),最大的不足是忽略文本上下文关系,每个词之间彼此独立,并且无法表征语义信息。词袋模型的示例如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
( 0, 0, 0, 0, .... , 1, ... 0, 0, 0, 0)
&/code&&/pre&&/div&&p&一般来说词库量至少都是百万级别,因此词袋模型有个两个最大的问题:高纬度、高稀疏性。词袋模型是向量空间模型的基础,因此向量空间模型通过特征项选择降低维度,通过特征权重计算增加稠密性。&/p&&br&&p&&b&特征提取:&/b&&/p&&p&向量空间模型的文本表示方法的特征提取对应特征项的选择和特征权重计算两部分。特征选择的基本思路是根据某个评价指标独立的对原始特征项(词项)进行评分排序,从中选择得分最高的一些特征项,过滤掉其余的特征项。常用的评价有文档频率、互信息、信息增益、χ?统计量等。&/p&&p&特征权重主要是经典的TF-IDF方法及其扩展方法,主要思路是一个词的重要度与在类别内的词频成正比,与所有类别出现的次数成反比。&/p&&p&&b&3)基于语义的文本表示&/b&&/p&&p&传统做法在文本表示方面除了向量空间模型,还有基于语义的文本表示方法,比如LDA主题模型、LSI/PLSI概率潜在语义索引等方法,一般认为这些方法得到的文本表示可以认为文档的深层表示,而word embedding文本分布式表示方法则是深度学习方法的重要基础,下文会展现。&/p&&p&&b&1.2 分类器&/b&&/p&&p&分类器基本都是统计分类方法了,基本上大部分机器学习方法都在文本分类领域有所应用,比如朴素贝叶斯分类算法(Na?ve Bayes)、KNN、SVM、最大熵和神经网络等等,传统分类模型不是本文重点,在这里就不展开了。&br&&/p&&br&&br&&h2&&b&二、深度学习&/b&&b&文本分类&/b&&b&方法&/b&&/h2&&p&上文介绍了传统的文本分类做法,传统做法主要问题的文本表示是高纬度高稀疏的,特征表达能力很弱,而且神经网络很不擅长对此类数据的处理;此外需要人工进行特征工程,成本很高。而深度学习最初在之所以图像和语音取得巨大成功,一个很重要的原因是图像和语音原始数据是连续和稠密的,有局部相关性,。应用深度学习解决大规模文本分类问题最重要的是解决文本表示,再利用CNN/RNN等网络结构自动获取特征表达能力,去掉繁杂的人工特征工程,端到端的解决问题。接下来会分别介绍:&/p&&br&&p&&b&2.1 文本的分布式表示:词向量(word embedding)&/b&&/p&&br&&p&分布式表示(Distributed Representation)其实Hinton 最早在1986年就提出了,基本思想是将每个词表达成 n 维稠密、连续的实数向量,与之相对的one-hot encoding向量空间只有一个维度是1,其余都是0。分布式表示最大的优点是具备非常powerful的特征表达能力,比如 n 维向量每维 k 个值,可以表征 &img src=&/equation?tex=k%5E%7Bn%7D& alt=&k^{n}& eeimg=&1&& 个概念。事实上,不管是神经网络的隐层,还是多个潜在变量的概率主题模型,都是应用分布式表示。下图是03年Bengio在 &a href=&/?target=http%3A//www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&A Neural Probabilistic Language Model&i class=&icon-external&&&/i&&/a& 的网络结构:&/p&&br&&p&&img src=&/v2-dc007baa415cf419cc2de_b.png& data-rawwidth=&1164& data-rawheight=&666& class=&origin_image zh-lightbox-thumb& width=&1164& data-original=&/v2-dc007baa415cf419cc2de_r.png&&这篇文章提出的神经网络语言模型(NNLM,Neural Probabilistic Language Model)采用的是文本分布式表示,即每个词表示为稠密的实数向量。NNLM模型的目标是构建语言模型:&/p&&p&&img src=&/v2-855f785db4_b.png& data-rawwidth=&1054& data-rawheight=&54& class=&origin_image zh-lightbox-thumb& width=&1054& data-original=&/v2-855f785db4_r.png&&词的分布式表示即词向量(word embedding)是训练语言模型的一个附加产物,即图中的Matrix C。&/p&&br&尽管Hinton 86年就提出了词的分布式表示,Bengio 03年便提出了NNLM,词向量真正火起来是google Mikolov 13年发表的两篇word2vec的文章 &a href=&/?target=http%3A//ttic.uchicago.edu/%7Ehaotang/speech/.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Efficient Estimation of Word Representations in Vector Space&i class=&icon-external&&&/i&&/a& 和 &a href=&/?target=https%3A//papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Distributed Representations of Words and Phrases and their Compositionality&i class=&icon-external&&&/i&&/a&,更重要的是发布了简单好用的&a href=&/?target=https%3A///archive/p/word2vec/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&word2vec工具包&i class=&icon-external&&&/i&&/a&,在语义维度上得到了很好的验证,极大的推进了文本分析的进程。下图是文中提出的CBOW 和 Skip-Gram两个模型的结构,基本类似于NNLM,不同的是模型去掉了非线性隐层,预测目标不同,CBOW是上下文词预测当前词,Skip-Gram则相反。&img src=&/v2-04bfcae5251_b.png& data-rawwidth=&1420& data-rawheight=&610& class=&origin_image zh-lightbox-thumb& width=&1420& data-original=&/v2-04bfcae5251_r.png&&&p&除此之外,提出了Hierarchical Softmax 和 Negative Sample两个方法,很好的解决了计算有效性,事实上这两个方法都没有严格的理论证明,有些trick之处,非常的实用主义。详细的过程不再阐述了,有兴趣深入理解word2vec的,推荐读读这篇很不错的paper:&a href=&/?target=http%3A//www-personal.umich.edu/%7Eronxin/pdf/w2vexp.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&word2vec Parameter Learning Explained&i class=&icon-external&&&/i&&/a&。额外多提一点,实际上word2vec学习的向量和真正语义还有差距,更多学到的是具备相似上下文的词,比如“good”“bad”相似度也很高,反而是文本分类任务输入有监督的语义能够学到更好的语义表示,有机会后续系统分享下。&/p&&p&至此,文本的表示通过词向量的表示方式,把文本数据从高纬度高稀疏的神经网络难处理的方式,变成了类似图像、语音的的连续稠密数据。深度学习算法本身有很强的数据迁移性,很多之前在图像领域很适用的深度学习算法比如CNN等也可以很好的迁移到文本领域了,下一小节具体阐述下文本分类领域深度学习的方法。&/p&&br&&p&&b&2.2 深度学习文本分类模型&/b&&/p&&p&词向量解决了文本表示的问题,该部分介绍的文本分类模型则是利用CNN/RNN等深度学习网络及其变体解决自动特征提取(即特征表达)的问题。&/p&&br&&p&&b&1)fastText&/b&&/p&&p&fastText 是上文提到的 word2vec 作者 Mikolov 转战 Facebook 后16年7月刚发表的一篇论文 &a href=&/?target=https%3A//arxiv.org/pdf/v2.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bag of Tricks for Efficient Text Classification&i class=&icon-external&&&/i&&/a&。把 fastText 放在此处并非因为它是文本分类的主流做法,而是它极致简单,模型图见下:&/p&&img src=&/v2-ec902f184beacb26cebd4dd_b.png& data-rawwidth=&2062& data-rawheight=&1046& class=&origin_image zh-lightbox-thumb& width=&2062& data-original=&/v2-ec902f184beacb26cebd4dd_r.png&&&p&原理是把句子中所有的词向量进行平均(某种意义上可以理解为只有一个avg pooling特殊CNN),然后直接接 softmax 层。其实文章也加入了一些 n-gram 特征的 trick 来捕获局部序列信息。文章倒没太多信息量,算是“水文”吧,带来的思考是文本分类问题是有一些“线性”问题的部分[from项亮],也就是说不必做过多的非线性转换、特征组合即可捕获很多分类信息,因此有些任务即便简单的模型便可以搞定了。&/p&&p&&b&2)TextCNN&/b&&br&&/p&&p&本篇文章的题图选用的就是14年这篇文章提出的TextCNN的结构(见下图)。fastText 中的网络结果是完全没有考虑词序信息的,而它用的 n-gram 特征 trick 恰恰说明了局部序列信息的重要意义。&a href=&/?target=http%3A//colah.github.io/posts/2014-07-Understanding-Convolutions/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&卷积神经网络(CNN Convolutional Neural Network)&i class=&icon-external&&&/i&&/a&最初在图像领域取得了巨大成功,CNN原理就不讲了,核心点在于可以&b&捕捉局部相关性&/b&,具体到文本分类任务中可以利用CNN来提取句子中类似 n-gram 的关键信息。&/p&&img src=&/v2-ab904178abfe2d0fa7c0584_b.png& data-rawwidth=&1702& data-rawheight=&642& class=&origin_image zh-lightbox-thumb& width=&1702& data-original=&/v2-ab904178abfe2d0fa7c0584_r.png&&&p&TextCNN的详细过程原理图见下:&br&&/p&&p&&img src=&/v2-bb10ad5bbdcf887e60a6_b.png& data-rawwidth=&1270& data-rawheight=&1244& class=&origin_image zh-lightbox-thumb& width=&1270& data-original=&/v2-bb10ad5bbdcf887e60a6_r.png&&&b&TextCNN详细过程&/b&:第一层是图中最左边的7乘5的句子矩阵,每行是词向量,维度=5,这个可以类比为图像中的原始像素点了。然后经过有 filter_size=(2,3,4) 的一维卷积层,每个filter_size 有两个输出 channel。第三层是一个1-max pooling层,这样不同长度句子经过pooling层之后都能变成定长的表示了,最后接一层全连接的 softmax 层,输出每个类别的概率。&/p&&p&&b&特征&/b&:这里的特征就是词向量,有静态(static)和非静态(non-static)方式。static方式采用比如word2vec预训练的词向量,训练过程不更新词向量,实质上属于迁移学习了,特别是数据量比较小的情况下,采用静态的词向量往往效果不错。non-static则是在训练过程中更新词向量。推荐的方式是 non-static 中的 fine-tunning方式,它是以预训练(pre-train)的word2vec向量初始化词向量,训练过程中调整词向量,能加速收敛,当然如果有充足的训练数据和资源,直接随机初始化词向量效果也是可以的。&/p&&br&&p&&b&通道(Channels)&/b&:图像中可以利用 (R, G, B) 作为不同channel,而文本的输入的channel通常是不同方式的embedding方式(比如 word2vec或Glove),实践中也有利用静态词向量和fine-tunning词向量作为不同channel的做法。&br&&/p&&p&&b&一维卷积(conv-1d)&/b&:图像是二维数据,经过词向量表达的文本为一维数据,因此在TextCNN卷积用的是一维卷积。一维卷积带来的问题是需要设计通过不同 filter_size 的 filter 获取不同宽度的视野。&/p&&p&&b&Pooling层&/b&:利用CNN解决文本分类问题的文章还是很多的,比如这篇 &a href=&/?target=https%3A//arxiv.org/pdf/.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&A Convolutional Neural Network for Modelling Sentences&i class=&icon-external&&&/i&&/a& 最有意思的输入是在 pooling 改成 (dynamic) k-max pooling ,pooling阶段保留 k 个最大的信息,保留了全局的序列信息。比如在情感分析场景,举个例子:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
“ 我觉得这个地方景色还不错,但是人也实在太多了 ”
&/code&&/pre&&/div&&p&虽然前半部分体现情感是正向的,全局文本表达的是偏负面的情感,利用 k-max pooling能够很好捕捉这类信息。&/p&&p&&b&3)TextRNN&/b&&/p&&br&&p&尽管TextCNN能够在很多任务里面能有不错的表现,但CNN有个最大问题是固定 filter_size 的视野,一方面无法建模更长的序列信息,另一方面 filter_size 的超参调节也很繁琐。CNN本质是做文本的特征表达工作,而自然语言处理中更常用的是递归神经网络(RNN, Recurrent Neural Network),能够更好的表达上下文信息。具体在文本分类任务中,Bi-directional RNN(实际使用的是双向LSTM)从某种意义上可以理解为可以捕获变长且双向的的 &n-gram& 信息。&/p&&p&RNN算是在自然语言处理领域非常一个标配网络了,在序列标注/命名体识别/seq2seq模型等很多场景都有应用,&a href=&/?target=https%3A//www.ijcai.org/Proceedings/16/Papers/408.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Recurrent Neural Network for Text Classification with Multi-Task Learning&i class=&icon-external&&&/i&&/a&文中介绍了RNN用于分类问题的设计,下图LSTM用于网络结构原理示意图,示例中的是利用最后一个词的结果直接接全连接层softmax输出了。&/p&&br&&img src=&/v2-92e49aef6626add56e85c2ee1b36e9aa_b.png& data-rawwidth=&728& data-rawheight=&228& class=&origin_image zh-lightbox-thumb& width=&728& data-original=&/v2-92e49aef6626add56e85c2ee1b36e9aa_r.png&&&br&&p&&b&4)TextRNN + Attention&/b&&/p&&p&CNN和RNN用在文本分类任务中尽管效果显著,但都有一个不足的地方就是不够直观,可解释性不好,特别是在分析badcase时候感受尤其深刻。而注意力(Attention)机制是自然语言处理领域一个常用的建模长时间记忆机制,能够很直观的给出每个词对结果的贡献,基本成了Seq2Seq模型的标配了。实际上文本分类从某种意义上也可以理解为一种特殊的Seq2Seq,所以考虑把Attention机制引入近来,研究了下学术界果然有类似做法。&/p&&br&&p&&b&Attention机制介绍&/b&:&/p&&p&详细介绍Attention恐怕需要一小篇文章的篇幅,感兴趣的可参考14年这篇paper &a href=&/?target=https%3A//arxiv.org/pdf/.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NEURAL MACHINE TRANSLATION BY JOINTLY LEARNING TO ALIGN AND TRANSLATE&i class=&icon-external&&&/i&&/a&。&/p&&p&以机器翻译为例简单介绍下,下图中 &img src=&/equation?tex=x_%7Bt%7D& alt=&x_{t}& eeimg=&1&& 是源语言的一个词,&img src=&/equation?tex=y_%7Bt%7D& alt=&y_{t}& eeimg=&1&& 是目标语言的一个词,机器翻译的任务就是给定源序列得到目标序列。翻译 &img src=&/equation?tex=y_%7Bt%7D& alt=&y_{t}& eeimg=&1&& 的过程产生取决于上一个词 &img src=&/equation?tex=y_%7Bt-1%7D& alt=&y_{t-1}& eeimg=&1&& 和源语言的词的表示 &img src=&/equation?tex=h_%7Bj%7D& alt=&h_{j}& eeimg=&1&&(&img src=&/equation?tex=x_%7Bj%7D& alt=&x_{j}& eeimg=&1&& 的 bi-RNN 模型的表示),而每个词所占的权重是不一样的。比如源语言是中文 “我 / 是 / 中国人” 目标语言 “i / am / Chinese”,翻译出“Chinese”时候显然取决于“中国人”,而与“我 / 是”基本无关。下图公式, &img src=&/equation?tex=%5Calpha+_%7Bij%7D& alt=&\alpha _{ij}& eeimg=&1&& 则是翻译英文第 &img src=&/equation?tex=i& alt=&i& eeimg=&1&& 个词时,中文第 &img src=&/equation?tex=j& alt=&j& eeimg=&1&& 个词的贡献,也就是注意力。显然在翻译“Chinese”时,“中国人”的注意力值非常大。&/p&&img src=&/v2-dedfe7efcf12ae_b.png& data-rawwidth=&2104& data-rawheight=&922& class=&origin_image zh-lightbox-thumb& width=&2104& data-original=&/v2-dedfe7efcf12ae_r.png&&&img src=&/v2-0ebc7b64a7d34a908b8d82d87c92f6b8_b.png& data-rawwidth=&1824& data-rawheight=&156& class=&origin_image zh-lightbox-thumb& width=&1824& data-original=&/v2-0ebc7b64a7d34a908b8d82d87c92f6b8_r.png&&&p&Attention的核心point是在翻译每个目标词(或 预测商品标题文本所属类别)所用的上下文是不同的,这样的考虑显然是更合理的。&/p&&p&&b&TextRNN + Attention 模型&/b&:&br&&/p&&p&我们参考了这篇文章 &a href=&/?target=https%3A//www.cs.cmu.edu/%7Ediyiy/docs/naacl16.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Hierarchical Attention Networks for Document Classification&i class=&icon-external&&&/i&&/a&,下图是模型的网络结构图,它一方面用层次化的结构保留了文档的结构,另一方面在word-level和sentence-level。淘宝标题场景只需要 word-level 这一层的 Attention 即可。&/p&&p&&img src=&/v2-4ff2c8099ccf0b2d8eb963a0ac248296_b.png& data-rawwidth=&2042& data-rawheight=&1356& class=&origin_image zh-lightbox-thumb& width=&2042& data-original=&/v2-4ff2c8099ccf0b2d8eb963a0ac248296_r.png&&加入Attention之后最大的好处自然是能够直观的解释各个句子和词对分类类别的重要性。&/p&&p&&b&5)TextRCNN(TextRNN + CNN)&/b&&/p&&p&我们参考的是中科院15年发表在AAAI上的这篇文章 Recurrent Convolutional Neural Networks for Text Classification 的结构:&/p&&br&&img src=&/v2-c0941fece21de00065aa92_b.png& data-rawwidth=&1508& data-rawheight=&590& class=&origin_image zh-lightbox-thumb& width=&1508& data-original=&/v2-c0941fece21de00065aa92_r.png&&&p&利用前向和后向RNN得到每个词的前向和后向上下文的表示:&/p&&img src=&/v2-d97b136cbb9cde0fd8b4_b.png& data-rawwidth=&1022& data-rawheight=&102& class=&origin_image zh-lightbox-thumb& width=&1022& data-original=&/v2-d97b136cbb9cde0fd8b4_r.png&&&p&这样词的表示就变成词向量和前向后向上下文向量concat起来的形式了,即:&br&&img src=&/v2-152ed3f73_b.png& data-rawwidth=&976& data-rawheight=&44& class=&origin_image zh-lightbox-thumb& width=&976& data-original=&/v2-152ed3f73_r.png&&最后再接跟TextCNN相同卷积层,pooling层即可,唯一不同的是卷积层 filter_size = 1就可以了,不再需要更大 filter_size 获得更大视野,这里词的表示也可以只用双向RNN输出。&/p&&br&&br&&h2&&b&三、一点经验&/b&&/h2&&p&理论和实践之间的Gap往往差异巨大,学术paper更关注的是模型架构设计的新颖性等,更重要的是新的思路;而实践最重要的是在落地场景的效果,关注的点和方法都不一样。这部分简单梳理实际做项目过程中的一点经验教训。&/p&&br&&p&&b&模型显然并不是最重要的&/b&:不能否认,好的模型设计对拿到好结果的至关重要,也更是学术关注热点。但实际使用中,模型的工作量占的时间其实相对比较少。虽然再第二部分介绍了5种CNN/RNN及其变体的模型,实际中文本分类任务单纯用CNN已经足以取得很不错的结果了,我们的实验测试RCNN对准确率提升大约1%,并不是十分的显著。最佳实践是先用TextCNN模型把整体任务效果调试到最好,再尝试改进模型。&/p&&p&&b&理解你的数据&/b&:虽然应用深度学习有一个很大的优势是不再需要繁琐低效的人工特征工程,然而如果你只是把他当做一个黑盒,难免会经常怀疑人生。一定要理解你的数据,记住无论传统方法还是深度学习方法,数据 sense 始终非常重要。要重视 badcase 分析,明白你的数据是否适合,为什么对为什么错。&/p&&br&&p&&b&关注迭代质量 - 记录和分析你的每次实验:&/b&迭代速度是决定算法项目成败的关键,学过概率的同学都很容易认同。而算法项目重要的不只是迭代速度,一定要关注迭代质量。如果你没有搭建一个快速实验分析的套路,迭代速度再快也只会替你公司心疼宝贵的计算资源。建议记录每次实验,实验分析至少回答这三个问题:为什么要实验?结论是什么?下一步怎么实验?&/p&&p&&b&超参调节&/b&:超参调节是各位调参工程师的日常了,推荐一篇文本分类实践的论文 &a href=&/?target=https%3A//arxiv.org/pdf/.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification&i class=&icon-external&&&/i&&/a&,里面贴了一些超参的对比实验,如果你刚开始启动文本分析任务,不妨按文章的结果设置超参,怎么最快的得到超参调节其实是一个非常重要的问题,可以读读 萧瑟的这篇文章 &a href=&/p/?utm_source=zhihu&utm_medium=social& class=&internal&&深度学习网络调参技巧 - 知乎专栏&/a&。&/p&&br&&p&&b&一定要用 &/b&&b&dropout&/b&&b&:&/b&有两种情况可以不用:数据量特别小,或者你用了更好的正则方法,比如bn。实际中我们尝试了不同参数的dropout,最好的还是0.5,所以如果你的计算资源很有限,默认0.5是一个很好的选择。&br&&/p&&p&&b&fine-tuning 是必选的&/b&:上文聊到了,如果只是使用word2vec训练的词向量作为特征表示,我赌你一定会损失很大的效果。&/p&&p&&b&未必一定要 softmax loss&/b&: 这取决与你的数据,如果你的任务是多个类别间非互斥,可以试试着训练多个二分类器,也就是把问题定义为multi lable 而非 multi class,我们调整后准确率还是增加了&1%。&br&&/p&&p&&b&类目不均衡问题&/b&:基本是一个在很多场景都验证过的结论:如果你的loss被一部分类别dominate,对总体而言大多是负向的。建议可以尝试类似 booststrap 方法调整 loss 中样本权重方式解决。&br&&/p&&br&&p&&b&避免训练震荡&/b&:默认一定要增加随机采样因素尽可能使得数据分布iid,默认shuffle机制能使得训练结果更稳定。如果训练模型仍然很震荡,可以考虑调整学习率或 mini_batch_size。&/p&&p&&b&没有收敛前不要过早的下结论&/b&:玩到最后的才是玩的最好的,特别是一些新的角度的测试,不要轻易否定,至少要等到收敛吧。&/p&&br&&br&&h2&&b&四、写在最后&/b&&/h2&&p&几年前校招面阿里时,一面二面聊的都是一个文本分类的项目(一个新浪微博主题分类的学校课题项目),用的还是文中介绍的传统的做法。面试时对特征项处理和各个分类器可谓如数家珍,被要求在白板上写了好几个特征选择公式,短短几年传统做法已经被远远超越,不得不感慨深度学习的发展。&/p&&p&值得感慨的一方面是今天技术的发展非常快,故步自封自然是万万万万不可取,深知还有很多理论尚且不懂还要继续深读paper;另一方面,理解理论原理和做好项目间实际非常有巨大的gap,特别是身处工业界的同仁们,学术圈值得钻但要把握分寸,如果仅仅追逐技术深度,不免容易陷入空中阁楼。&/p&&br&&p&最后老规矩再次安利下我们team的招聘,对淘宝搜索排序和自然语言处理方向感兴趣的同学欢迎邮件我 qingsong.,来淘宝,一起成长!&/p&&p&以上,感谢阅读。&/p&
近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文题目便是文本分类问题,趁此机会总结下文本分类领域特别是应用深度学习解决文本分类的相关的思路、做法和部分实践的经验。业务问题描述:淘宝商品的一个典型的例子见下图…
&h3&手机端运行卷积神经网络的一次实践 — 基于 TensorFlow 和 OpenCV 实现文档检测功能&/h3&&p&&b&作者:冯牮&/b&&/p&
&h2&1. 前言&/h2&
&p& 本文不是神经网络或机器学习的入门教学,而是通过一个真实的产品案例,展示了在手机客户端上运行一个神经网络的关键技术点&/p&
&p& 在卷积神经网络适用的领域里,已经出现了一些很经典的图像分类网络,比如 VGG16/VGG19,Inception v1-v4 Net,ResNet 等,这些分类网络通常又都可以作为其他算法中的基础网络结构,尤其是 VGG 网络,被很多其他的算法借鉴,本文也会使用 VGG16 的基础网络结构,但是不会对 VGG 网络做详细的入门教学&/p&
&p& 虽然本文不是神经网络技术的入门教程,但是仍然会给出一系列的相关入门教程和技术文档的链接,有助于进一步理解本文的内容&/p&
&p& 具体使用到的神经网络算法,只是本文的一个组成部分,除此之外,本文还介绍了如何裁剪 TensorFlow 静态库以便于在手机端运行,如何准备训练样本图片,以及训练神经网络时的各种技巧等等&/p&
&h2&2. 需求是什么&/h2&
&img src=&/v2-2e9bce2f0dfaf2b2b9bc5_b.png& data-rawwidth=&1000& data-rawheight=&546& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-2e9bce2f0dfaf2b2b9bc5_r.png&&&p& 需求很容易描述清楚,如上图,&strong&就是在一张图里,把矩形形状的文档的四个顶点的坐标找出来。&/strong&&/p&
&h2&3. 传统的技术方案&/h2&
&p& Google 搜索 opencv scan document,是可以找到好几篇相关的教程的,这些教程里面的技术手段,也都大同小异,关键步骤就是调用 OpenCV 里面的两个函数,cv2.Canny() 和 cv2.findContours()。&/p&
&img src=&/v2-da8d55f2eaf9ef6_b.png& data-rawwidth=&1000& data-rawheight=&750& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-da8d55f2eaf9ef6_r.png&&&p& 看上去很容易就能实现出来,但是真实情况是,这些教程,仅仅是个 demo 演示而已,用来演示的图片,都是最理想的简单情况,真实的场景图片会比这个复杂的多,会有各种干扰因素,调用 canny 函数得到的边缘检测结果,也会比 demo 中的情况凌乱的多,比如会检测出很多各种长短的线段,或者是文档的边缘线被截断成了好几条短的线段,线段之间还存在距离不等的空隙。另外,findContours 函数也只能检测闭合的多边形的顶点,但是并不能确保这个多边形就是一个合理的矩形。因此在我们的第一版技术方案中,对这两个关键步骤,进行了大量的改进和调优,概括起来就是:&/p&
&ul&&li&&p&改进 canny 算法的效果,增加额外的步骤,得到效果更好的边缘检测图&/p&
&li&&p&针对 canny 步骤得到的边缘图,建立一套数学算法,从边缘图中寻找出一个合理的矩形区域&/p&
&/ul&&h2&4. 传统技术方案的难度和局限性&/h2&
&ul&&li&&p&canny 算法的检测效果,依赖于几个阈值参数,这些阈值参数的选择,通常都是人为设置的经验值,在改进的过程中,引入额外的步骤后,通常又会引入一些新的阈值参数,同样,也是依赖于调试结果设置的经验值。整体来看,这些阈值参数的个数,不能特别的多,因为一旦太多了,就很难依赖经验值进行设置,另外,虽然有这些阈值参数,但是最终的参数只是一组或少数几组固定的组合,所以算法的鲁棒性又会打折扣,很容易遇到边缘检测效果不理想的场景&/p&
&li&&p&在边缘图上建立的数学模型很复杂,代码实现难度大,而且也会遇到算法无能为力的场景&/p&
&/ul&&p& 下面这张图表,能够很好的说明上面列出的这两个问题:&/p&
&img src=&/v2-aab128a10b584f6_b.png& data-rawwidth=&1000& data-rawheight=&1334& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-aab128a10b584f6_r.png&&&p& 这张图表的第一列是输入的 image,最后的三列(先不用看这张图表的第二列),是用三组不同阈值参数调用 canny 函数和额外的函数后得到的输出 image,可以看到,边缘检测的效果,并不总是很理想的,有些场景中,矩形的边,出现了很严重的断裂,有些边,甚至被完全擦除掉了,而另一些场景中,又会检测出很多干扰性质的长短边。可想而知,想用一个数学模型,适应这么不规则的边缘图,会是多么困难的一件事情。&/p&
&h2&5. 思考如何改善&/h2&
&p& 在第一版的技术方案中,负责的同学花费了大量的精力进行各种调优,终于取得了还不错的效果,但是,就像前面描述的那样,还是会遇到检测不出来的场景。在第一版技术方案中,遇到这种情况的时候,采用的做法是针对这些不能检测的场景,人工进行分析和调试,调整已有的一组阈值参数和算法,可能还需要加入一些其他的算法流程(可能还会引入新的一些阈值参数),然后再整合到原有的代码逻辑中。经过若干轮这样的调整后,我们发现,已经进入一个瓶颈,按照这种手段,很难进一步提高检测效果了。&/p&
&p&&strong&既然传统的算法手段已经到极限了,那不如试试机器学习/神经网络。&/strong&&/p&
&h2&6. 无效的神经网络算法&/h2&
&h4&6.1 end-to-end 直接拟合&/h4&
&p& 首先想到的,就是仿照人脸对齐(face alignment)的思路,构建一个端到端(end-to-end)的网络,直接回归拟合,也就是让这个神经网络直接输出 4 个顶点的坐标,但是,经过尝试后发现,根本拟合不出来。后来仔细琢磨了一下,觉得不能直接拟合也是对的,因为:&/p&
&ul&&li&除了分类(classification)问题之外,所有的需求看上去都像是一个回归(regression)问题,如果回归是万能的,学术界为啥还要去搞其他各种各样的网络模型&/li&
&li&face alignment 之所以可以用回归网络得到很好的拟合效果,是因为在输入 image 上先做了 bounding box 检测,缩小了人脸图像范围后,才做的 regression&/li&
&li&人脸上的关键特征点,具有特别明显的统计学特征,所以 regression 可以发挥作用&/li&
&li&在需要更高检测精度的场景中,其实也是用到了更复杂的网络模型来解决 face alignment 问题的&/li&
&/ul&&h4&6.2 YOLO && FCN&/h4&
&p& 后来还尝试过用 YOLO 网络做 Object Detection,用 FCN 网络做像素级的 Semantic Segmentation,但是结果都很不理想,比如:&/p&
&ul&&li&达不到文档检测功能想要的精确度&/li&
&li&网络结构复杂,运算量大,在手机上无法做到实时检测&/li&
&/ul&&h2&7. 有效的神经网络算法&/h2&
&p& 前面尝试的几种神经网络算法,都不能得到想要的效果,后来换了一种思路,既然传统的技术手段里包含了两个关键的步骤,那能不能用神经网络来分别改善这两个步骤呢,经过分析发现,可以尝试用神经网络来替换 canny 算法,也就是用神经网络来对图像中的矩形区域进行边缘检测,只要这个边缘检测能够去除更多的干扰因素,那第二个步骤里面的算法也就可以变得更简单了。&/p&
&h4&7.1 神经网络的输入和输出&/h4&
&img src=&/v2-c51a36afee5_b.png& data-rawwidth=&1000& data-rawheight=&390& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-c51a36afee5_r.png&&&p& 按照这种思路,对于神经网络部分,现在的需求变成了上图所示的样子。&/p&
&h4&7.2 HED(Holistically-Nested Edge Detection) 网络&/h4&
&p& 边缘检测这种需求,在图像处理领域里面,通常叫做 Edge Detection 或 Contour Detection,按照这个思路,找到了 Holistically-Nested Edge Detection 网络模型。&/p&
&p& HED 网络模型是在 VGG16 网络结构的基础上设计出来的,所以有必要先看看 VGG16。&/p&
&img src=&/v2-d211f582d53ad1cedafed1a_b.png& data-rawwidth=&1000& data-rawheight=&437& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-d211f582d53ad1cedafed1a_r.png&&&p& 上图是 VGG16 的原理图,为了方便从 VGG16 过渡到 HED,我们先把 VGG16 变成下面这种示意图:&/p&
&img src=&/v2-b37f470f5d32b6ddc545f5b23ae4f34d_b.png& data-rawwidth=&1000& data-rawheight=&750& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-b37f470f5d32b6ddc545f5b23ae4f34d_r.png&&&p& 在上面这个示意图里,用不同的颜色区分了 VGG16 的不同组成部分。&/p&
&img src=&/v2-a8a2bc9a254ad600cd4dde2d8b55c8b4_b.png& data-rawwidth=&1000& data-rawheight=&750& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-a8a2bc9a254ad600cd4dde2d8b55c8b4_r.png&&&p& 从示意图上可以看到,绿色代表的卷积层和红色代表的池化层,可以很明显的划分出五组,上图用紫色线条框出来的就是其中的第三组。&/p&
&img src=&/v2-a37e686d317e8de722e660cf6dec80c2_b.png& data-rawwidth=&1000& data-rawheight=&487& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-a37e686d317e8de722e660cf6dec80c2_r.png&&&p& HED 网络要使用的就是 VGG16 网络里面的这五组,后面部分的 fully connected 层和 softmax 层,都是不需要的,另外,第五组的池化层(红色)也是不需要的。&/p&
&img src=&/v2-6bcc8fcd4cf363e64796_b.png& data-rawwidth=&1000& data-rawheight=&461& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-6bcc8fcd4cf363e64796_r.png&&&p& 去掉不需要的部分后,就得到上图这样的网络结构,因为有池化层的作用,从第二组开始,每一组的输入 image 的长宽值,都是前一组的输入 image 的长宽值的一半。&/p&
&img src=&/v2-bfd6d7db247bbebca71aa_b.png& data-rawwidth=&1000& data-rawheight=&750& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-bfd6d7db247bbebca71aa_r.png&&&p& HED 网络是一种多尺度多融合(multi-scale and multi-level feature learning)的网络结构,所谓的多尺度,就是如上图所示,把 VGG16 的每一组的最后一个卷积层(绿色部分)的输出取出来,因为每一组得到的 image 的长宽尺寸是不一样的,所以这里还需要用转置卷积(transposed convolution)/反卷积(deconv)对每一组得到的 image 再做一遍运算,从效果上看,相当于把第二至五组得到的 image 的长宽尺寸分别扩大 2 至 16 倍,这样在每个尺度(VGG16 的每一组就是一个尺度)上得到的 image,都是相同的大小了。&/p&
&img src=&/v2-da38de5068ebf113863f_b.png& data-rawwidth=&1000& data-rawheight=&750& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-da38de5068ebf113863f_r.png&&&p& 把每一个尺度上得到的相同大小的 image,再融合到一起,这样就得到了最终的输出 image,也就是具有边缘检测效果的 image。&/p&
&p& 基于 TensorFlow 编写的 HED 网络结构代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& def hed_net(inputs, batch_size):
# ref s9xie/hed
with tf.variable_scope('hed', 'hed', [inputs]):
with slim.arg_scope([slim.conv2d, slim.fully_connected],
activation_fn=tf.nn.relu,
weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
weights_regularizer=slim.l2_regularizer(0.0005)):
# vgg16 conv && max_pool layers
net = slim.repeat(inputs, 2, slim.conv2d, 12, [3, 3], scope='conv1')
dsn1 = net
net = slim.max_pool2d(net, [2, 2], scope='pool1')
net = slim.repeat(net, 2, slim.conv2d, 24, [3, 3], scope='conv2')
dsn2 = net
net = slim.max_pool2d(net, [2, 2], scope='pool2')
net = slim.repeat(net, 3, slim.conv2d, 48, [3, 3], scope='conv3')
dsn3 = net
net = slim.max_pool2d(net, [2, 2], scope='pool3')
net = slim.repeat(net, 3, slim.conv2d, 96, [3, 3], scope='conv4')
dsn4 = net
net = slim.max_pool2d(net, [2, 2], scope='pool4')
net = slim.repeat(net, 3, slim.conv2d, 192, [3, 3], scope='conv5')
dsn5 = net
# net = slim.max_pool2d(net, [2, 2], scope='pool5') # no need this pool layer
# dsn layers
dsn1 = slim.conv2d(dsn1, 1, [1, 1], scope='dsn1')
# no need deconv for dsn1
dsn2 = slim.conv2d(dsn2, 1, [1, 1], scope='dsn2')
deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1])
dsn2 = deconv_mobile_version(dsn2, 2, deconv_shape) # deconv_mobile_version can work on mobile
dsn3 = slim.conv2d(dsn3, 1, [1, 1], scope='dsn3')
deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1])
dsn3 = deconv_mobile_version(dsn3, 4, deconv_shape)
dsn4 = slim.conv2d(dsn4, 1, [1, 1], scope='dsn4')
deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1])
dsn4 = deconv_mobile_version(dsn4, 8, deconv_shape)
dsn5 = slim.conv2d(dsn5, 1, [1, 1], scope='dsn5')
deconv_shape = tf.pack([batch_size, const.image_height, const.image_width, 1])
dsn5 = deconv_mobile_version(dsn5, 16, deconv_shape)
# dsn fuse
dsn_fuse = tf.concat(3, [dsn1, dsn2, dsn3, dsn4, dsn5])
dsn_fuse = tf.reshape(dsn_fuse, [batch_size, const.image_height, const.image_width, 5]) #without this, will get error: ValueError: Number of in_channels must be known.
dsn_fuse = slim.conv2d(dsn_fuse, 1, [1, 1], scope='dsn_fuse')
return dsn_fuse, dsn1, dsn2, dsn3, dsn4, dsn5
&/code&&/pre&&/div&&h2&8. 训练网络&/h2&
&h4&8.1 cost 函数&/h4&
&p& 论文给出的 HED 网络是一个通用的边缘检测网络,按照论文的描述,每一个尺度上得到的 image,都需要参与 cost 的计算,这部分的代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& input_queue_for_train = tf.train.string_input_producer([FLAGS.csv_path])
image_tensor, annotation_tensor = input_image_pipeline(dataset_root_dir_string, input_queue_for_train, FLAGS.batch_size)
dsn_fuse, dsn1, dsn2, dsn3, dsn4, dsn5 = hed_net(image_tensor, FLAGS.batch_size)
cost = class_balanced_sigmoid_cross_entropy(dsn_fuse, annotation_tensor) + \
class_balanced_sigmoid_cross_entropy(dsn1, annotation_tensor) + \
class_balanced_sigmoid_cross_entropy(dsn2, annotation_tensor) + \
class_balanced_sigmoid_cross_entropy(dsn3, annotation_tensor) + \
class_balanced_sigmoid_cross_entropy(dsn4, annotation_tensor) + \
class_balanced_sigmoid_cross_entropy(dsn5, annotation_tensor)
&/code&&/pre&&/div&&p& 按照这种方式训练出来的网络,检测到的边缘线是有一点粗的,为了得到更细的边缘线,通过多次试验找到了一种优化方案,代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& input_queue_for_train = tf.train.string_input_producer([FLAGS.csv_path])
image_tensor, annotation_tensor = input_image_pipeline(dataset_root_dir_string, input_queue_for_train, FLAGS.batch_size)
dsn_fuse, _, _, _, _, _ = hed_net(image_tensor, FLAGS.batch_size)
cost = class_balanced_sigmoid_cross_entropy(dsn_fuse, annotation_tensor)
&/code&&/pre&&/div&&p& 也就是不再让每个尺度上得到的 image 都参与 cost 的计算,只使用融合后得到的最终 image 来进行计算。&/p&
&p& 两种 cost 函数的效果对比如下图所示,右侧是优化过后的效果:&/p&
&img src=&/v2-212b579b2edd02a4d0b8d8af7d82f241_b.png& data-rawwidth=&1000& data-rawheight=&703& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-212b579b2edd02a4d0b8d8af7d82f241_r.png&&&p& 另外还有一点,按照 HED 论文里的要求,计算 cost 的时候,不能使用常见的方差 cost,而应该使用 cost-sensitive loss function,代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& def class_balanced_sigmoid_cross_entropy(logits, label, name='cross_entropy_loss'):
The class-balanced cross entropy loss,
as in `Holistically-Nested Edge Detection
&[] Holistically-Nested Edge Detection&`_.
This is more numerically stable than class_balanced_cross_entropy
:param logits: size: the logits.
:param label: size: the ground truth in {0,1}, of the same shape as logits.
:returns: a scalar. class-balanced cross entropy loss
y = tf.cast(label, tf.float32)
count_neg = tf.reduce_sum(1. - y) # the number of 0 in y
count_pos = tf.reduce_sum(y) # the number of 1 in y (less than count_neg)
beta = count_neg / (count_neg + count_pos)
pos_weight = beta / (1 - beta)
cost = tf.nn.weighted_cross_entropy_with_logits(logits, y, pos_weight)
cost = tf.reduce_mean(cost * (1 - beta), name=name)
return cost
&/code&&/pre&&/div&&h4&8.2 转置卷积层的双线性初始化&/h4&
&p& 在尝试 FCN 网络的时候,就被这个问题卡住过很长一段时间,按照 FCN 的要求,在使用转置卷积(transposed convolution)/反卷积(deconv)的时候,要把卷积核的值初始化成双线性放大矩阵(bilinear upsampling kernel),而不是常用的正态分布随机初始化,同时还要使用很小的学习率,这样才更容易让模型收敛。&/p&
&p& HED 的论文中,并没有明确的要求也要采用这种方式初始化转置卷积层,但是,在训练过程中发现,采用这种方式进行初始化,模型才更容易收敛。&/p&
&p& 这部分的代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& def get_kernel_size(factor):
Find the kernel size given the desired factor of upsampling.
return 2 * factor - factor % 2
def upsample_filt(size):
Make a 2D bilinear kernel suitable for upsampling of the given (h, w) size.
factor = (size + 1) // 2
if size % 2 == 1:
center = factor - 1
center = factor - 0.5
og = np.ogrid[:size, :size]
return (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
def bilinear_upsample_weights(factor, number_of_classes):
Create weights matrix for transposed convolution with bilinear filter
initialization.
filter_size = get_kernel_size(factor)
weights = np.zeros((filter_size,
filter_size,
number_of_classes,
number_of_classes), dtype=np.float32)
upsample_kernel = upsample_filt(filter_size)
for i in xrange(number_of_classes):
weights[:, :, i, i] = upsample_kernel
return weights
&/code&&/pre&&/div&&h4&8.3 训练过程冷启动&/h4&
&p& HED 网络不像 VGG 网络那样很容易就进入收敛状态,也不太容易进入期望的理想状态,主要是两方面的原因:&/p&
&ul&&li&前面提到的转置卷积层的双线性初始化,就是一个重要因素,因为在 4 个尺度上,都需要反卷积,如果反卷积层不能收敛,那整个 HED 都不会进入期望的理想状态&/li&
&li&另外一个原因,是由 HED 的多尺度引起的,既然是多尺度了,那每个尺度上得到的 image 都应该对模型的最终输出 image 产生贡献,在训练的过程中发现,如果输入 image 的尺寸是 224&em&224,还是很容易就训练成功的,但是当把输入 image 的尺寸调整为 256&/em&256 后,很容易出现一种状况,就是 5 个尺度上得到的 image,会有 1 ~ 2 个 image 是无效的(全部是黑色)&/li&
&/ul&&p& 为了解决这里遇到的问题,采用的办法就是先使用少量样本图片(比如 2000 张)训练网络,在很短的训练时间(比如迭代 1000 次)内,如果 HED 网络不能表现出收敛的趋势,或者不能达到 5 个尺度的 image 全部有效的状态,那就直接放弃这轮的训练结果,重新开启下一轮训练,直到满意为止,然后才使用完整的训练样本集合继续训练网络。&/p&
&h2&9. 训练数据集(大量合成数据 + 少量真实数据)&/h2&
&p& HED 论文里使用的训练数据集,是针对通用的边缘检测目的的,什么形状的边缘都有,比如下面这种:&/p&
&img src=&/v2-436a150b7abd6b32bbef612_b.png& data-rawwidth=&1000& data-rawheight=&290& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-436a150b7abd6b32bbef612_r.png&&&p& 用这份数据训练出来的模型,在做文档扫描的时候,检测出来的边缘效果并不理想,而且这份训练数据集的样本数量也很小,只有一百多张图片(因为这种图片的人工标注成本太高了),这也会影响模型的质量。&/p&
&p& 现在的需求里,要检测的是具有一定透视和旋转变换效果的矩形区域,所以可以大胆的猜测,如果准备一批针对性更强的训练样本,应该是可以得到更好的边缘检测效果的。&/p&
&p& 借助第一版技术方案收集回来的真实场景图片,我们开发了一套简单的标注工具,人工标注了 1200 张图片(标注这 1200 张图片的时间成本也很高),但是这 1200 多张图片仍然有很多问题,比如对于神经网络来说,1200 个训练样本其实还是不够的,另外,这些图片覆盖的场景其实也比较少,有些图片的相似度比较高,这样的数据放到神经网络里训练,泛化的效果并不好。&/p&
&p& 所以,还采用技术手段,合成了80000多张训练样本图片。&/p&
&img src=&/v2-fda059ae7ba_b.png& data-rawwidth=&1000& data-rawheight=&542& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-fda059ae7ba_r.png&&&p& 如上图所示,一张背景图和一张前景图,可以合成出一对训练样本数据。在合成图片的过程中,用到了下面这些技术和技巧:&/p&
&ul&&li&在前景图上添加旋转、平移、透视变换&/li&
&li&对背景图进行了随机的裁剪&/li&
&li&通过试验对比,生成合适宽度的边缘线&/li&
&li&OpenCV 不支持透明图层之间的旋转和透视变换操作,只能使用最低精度的插值算法,为了改善这一点,后续改成了使用 iOS 模拟器,通过 CALayer 上的操作来合成图片&/li&
&li&在不断改进训练样本的过程中,还根据真实样本图片的统计情况和各种途径的反馈信息,刻意模拟了一些更复杂的样本场景,比如凌乱的背景环境、直线边缘干扰等等&/li&
&/ul&&p& 经过不断的调整和优化,最终才训练出一个满意的模型,可以再次通过下面这张图表中的第二列看一下神经网络模型的边缘检测效果:&/p&
&img src=&/v2-aab128a10b584f6_b.png& data-rawwidth=&1000& data-rawheight=&1334& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-aab128a10b584f6_r.png&&&h2&10. 在手机设备上运行 TensorFlow&/h2&
&h4&10.1 在手机上使用 TensorFlow 库&/h4&
&p& TensorFlow 官方是支持 iOS 和 Android 的,而且有清晰的文档,照着做就行。但是因为 TensorFlow 是依赖于 protobuf 3 的,所以有可能会遇到一些其他的问题,比如下面这两种,就是我们在两个不同的 iOS APP 中遇到的问题和解决办法,可以作为一个参考:&/p&
&ul&&li&A 产品使用的是 protobuf 2,同时由于各种历史原因,使用并且停留在了很旧的某个版本的 Base 库上,而 protobuf 3 的内部也使用了 Base 库,当 A 产品升级到 protobuf 3 后,protobuf 3 的 Base 库和 A 源码中的 Base 库产生了一些奇怪的冲突,最后的解决办法是手动修改了 A 源码中的 Base 库,避免编译时的冲突&/li&
&li&B 产品也是使用的 protobuf 2,而且 B 产品使用到的多个第三方模块(没有源码,只有二进制文件)也是依赖于 protobuf 2,直接升级 B 产品使用的 protobuf 库就行不通了,最后采用的方法是修改 TensorFlow 和 TensorFlow 中使用的 protobuf 3 的源代码,把 protobuf 3 换了一个命名空间,这样两个不同版本的 protobuf 库就可以共存了&/li&
&/ul&&p& Android 上因为本身是可以使用动态库的,所以即便 app 必须使用 protobuf 2 也没有关系,不同的模块使用 dlopen 的方式加载各自需要的特定版本的库就可以了。&/p&
&h4&10.2 在手机上使用训练得到的模型文件&/h4&
&p& 模型通常都是在 PC 端训练的,对于大部分使用者,都是用 Python 编写的代码,得到 ckpt 格式的模型文件。在使用模型文件的时候,一种做法就是用代码重新构建出完整的神经网络,然后加载这个 ckpt 格式的模型文件,如果是在 PC 上使用模型文件,用这个方法其实也是可以接受的,复制粘贴一下 Python 代码就可以重新构建整个神经网络。但是,在手机上只能使用 TensorFlow 提供的 C++ 接口,如果还是用同样的思路,就需要用 C++ API 重新构建一遍神经网络,这个工作量就有点大了,而且 C++ API 使用起来比 Python API 复杂的多,所以,在 PC 上训练完网络后,还需要把 ckpt 格式的模型文件转换成 pb 格式的模型文件,这个 pb 格式的模型文件,是用 protobuf 序列化得到的二进制文件,里面包含了神经网络的具体结构以及每个矩阵的数值,使用这个 pb 文件的时候,不需要再用代码构建完整的神经网络结构,只需要反序列化一下就可以了,这样的话,用 C++ API 编写的代码就会简单很多,其实这也是 TensorFlow 推荐的使用方法,在 PC 上使用模型的时候,也应该使用这种 pb 文件(训练过程中使用 ckpt 文件)。&/p&
&h2&11. HED 网络在手机上遇到的奇怪 crash&/h2&
&p& 在手机上加载 pb 模型文件并且运行的时候,遇到过一个诡异的错误,内容如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& Invalid argument: No OpKernel was registered to support Op 'Mul' with these attrs.
Registered devices: [CPU], Registered kernels:
device='CPU'; T in [DT_FLOAT]
[[Node: hed/mul_1 = Mul[T=DT_INT32](hed/strided_slice_2, hed/mul_1/y)]]
&/code&&/pre&&/div&&p& 之所以诡异,是因为从字面上看,这个错误的含义是缺少乘法操作(Mul),但是我用其他的神经网络模型做过对比,乘法操作模块是可以正常工作的。&/p&
&p& Google 搜索后发现很多人遇到过类似的情况,但是错误信息又并不相同,后来在 TensorFlow 的 github issues 里终于找到了线索,综合起来解释,是因为 TensorFlow 是基于操作(Operation)来模块化设计和编码的,每一个数学计算模块就是一个 Operation,由于各种原因,比如内存占用大小、GPU 独占操作等等,mobile 版的 TensorFlow,并没有包含所有的 Operation,mobile 版的 TensorFlow 支持的 Operation 只是 PC 完整版 TensorFlow 的一个子集,我遇到的这个错误,就是因为使用到的某个 Operation 并不支持 mobile 版。&/p&
&p& 按照这个线索,在 Python 代码中逐个排查,后来定位到了出问题的代码,修改前后的代码如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& def deconv(inputs, upsample_factor):
input_shape = tf.shape(inputs)
# Calculate the ouput size of the upsampled tensor
upsampled_shape = tf.pack([input_shape[0],
input_shape[1] * upsample_factor,
input_shape[2] * upsample_factor,
upsample_filter_np = bilinear_upsample_weights(upsample_factor, 1)
upsample_filter_tensor = tf.constant(upsample_filter_np)
# Perform the upsampling
upsampled_inputs = tf.nn.conv2d_transpose(inputs, upsample_filter_tensor,
output_shape=upsampled_shape,
strides=[1, upsample_factor, upsample_factor, 1])
return upsampled_inputs
def deconv_mobile_version(inputs, upsample_factor, upsampled_shape):
upsample_filter_np = bilinear_upsample_weights(upsample_factor, 1)
upsample_filter_tensor = tf.constant(upsample_filter_np)
# Perform the upsampling
upsampled_inputs = tf.nn.conv2d_transpose(inputs, upsample_filter_tensor,
output_shape=upsampled_shape,
strides=[1, upsample_factor, upsample_factor, 1])
return upsampled_inputs
&/code&&/pre&&/div&&p& 问题就是由 deconv 函数中的 tf.shape 和 tf.pack 这两个操作引起的,在 PC 版代码中,为了简洁,是基于这两个操作,自动计算出 upsampled_shape,修改过后,则是要求调用者用 hard coding 的方式设置对应的 upsampled_shape。&/p&
&h2&12. 裁剪 TensorFlow&/h2&
&p& TensorFlow 是一个很庞大的框架,对于手机来说,它占用的体积是比较大的,所以需要尽量的缩减 TensorFlow 库占用的体积。&/p&
&p& 其实在解决前面遇到的那个 crash 问题的时候,已经指明了一种裁剪的思路,既然 mobile 版的 TensorFlow 本来就是 PC 版的一个子集,那就意味着可以根据具体的需求,让这个子集变得更小,这也就达到了裁剪的目的。具体来说,就是修改 TensorFlow 源码中的 tensorflow/tensorflow/contrib/makefile/tf_op_files.txt 文件,只保留使用到了的模块。针对 HED 网络,原有的 200 多个模块裁剪到只剩 46 个,裁剪过后的 tf_op_files.txt 文件如下:&/p&
&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& tensorflow/core/kernels/xent_op.cc
tensorflow/core/kernels/where_op.cc
tensorflow/core/kernels/unpack_op.cc
tensorflow/core/kernels/transpose_op.cc
tensorflow/core/kernels/transpose_functor_cpu.cc
tensorflow/core/kernels/tensor_array_ops.cc
tensorflow/core/kernels/tensor_array.cc
tensorflow/core/kernels/split_op.cc
tensorflow/core/kernels/split_v_op.cc
tensorflow/core/kernels/split_lib_cpu.cc
tensorflow/core/kernels/shape_ops.cc
tensorflow/core/kernels/session_ops.cc
tensorflow/core/kernels/sendrecv_ops.cc
tensorflow/core/kernels/reverse_op.cc
tensorflow/core/kernels/reshape_op.cc
tensorflow/core/kernels/relu_op.cc
tensorflow/core/kernels/pooling_ops_common.cc
tensorflow/core/kernels/pack_op.cc
tensorflow/core/kernels/ops_util.cc
tensorflow/core/kernels/no_op.cc
tensorflow/core/kernels/maxpooling_op.cc
tensorflow/core/kernels/matmul_op.cc
tensorflow/core/kernels/immutable_constant_op.cc
tensorflow/core/kernels/identity_op.cc
tensorflow/core/kernels/gather_op.cc
tensorflow/core/kernels/gather_functor.cc
tensorflow/core/kernels/fill_functor.cc
tensorflow/core/kernels/dense_update_ops.cc
tensorflow/core/kernels/deep_conv2d.cc
tensorflow/core/kernels/xsmm_conv2d.cc
tensorflow/core/kernels/conv_ops_using_gemm.cc
tensorflow/core/kernels/conv_ops_fused.cc
tensorflow/core/kernels/conv_ops.cc
tensorflow/core/kernels/conv_grad_filter_ops.cc
tensorflow/core/kernels/conv_grad_input_ops.cc
tensorflow/core/kernels/conv_grad_ops.cc
tensorflow/core/kernels/constant_op.cc
tensorflow/core/kernels/concat_op.cc
tensorflow/core/kernels/concat_lib_cpu.cc
tensorflow/core/kernels/bias_op.cc
tensorflow/core/ops/sendrecv_ops.cc
tensorflow/core/ops/no_op.cc
tensorflow/core/ops/nn_ops.cc
tensorflow/core/ops/nn_grad.cc
tensorflow/core/ops/array_ops.cc
tensorflow/core/ops/array_grad.cc
&/code&&/pre&&/div&&p& 需要强调的一点是,这种操作思路,是针对不同的神经网络结构有不同的裁剪方式,原则就是用到什么模块就保留什么模块。当然,因为有些模块之间还存在隐含的依赖关系,所以裁剪的时候也是要反复尝试多次才能成功的。&/p&
&p& 除此之外,还有下面这些通用手段也可以实现裁剪的目的:&/p&
&p& 编译器级别的 strip 操作,在链接的时候会自动的把没有调用到的函数去除掉(集成开发环境里通常已经自动将这些参数设置成了最佳组合)&br& 借助一些高级技巧和工具,对二进制文件进行瘦身&br& 借助所有这些裁剪手段,最终我们的 ipa 安装包的大小只增加了 3M。如果不做手动裁剪这一步,那 ipa 的增量,则是 30M 左右。&/p&
&h2&13. 裁剪 HED 网络&/h2&
&p& 按照 HED 论文给出的参考信息,得到的模型文件的大小是 56M,对于手机来说也是比较大的,而且模型越大也意味着计算量越大,所以需要考虑能否把 HED 网络也裁剪一下。&/p&
&p& HED 网络是用 VGG16 作为基础网络结构,而 VGG 又是一个得到广泛验证的基础网络结构,因此修改 HED 的整体结构肯定不是一个明智的选择,至少不是首选的方案。&/p&
&p& 考虑到现在的需求,只是检测矩形区域的边缘,而并不是检测通用场景下的广义的边缘,可以认为前者的复杂度比后者更低,所以一种可行的思路,就是保留 HED 的整体结构,修改 VGG 每一组卷积层里面的卷积核的数量,让 HED 网络变的更『瘦』。&/p&
&p& 按照这种思路,经过多次调整和尝试,最终得到了一组合适的卷积核的数量参数,对应的模型文件只有 4.2M,在 iPhone 7P 上,处理每帧图片的时间消耗是 0.1 秒左右,满足实时性的要求。&/p&
&p& 神经网络的裁剪,目前在学术界也是一个很热门的领域,有好几种不同的理论来实现不同目的的裁剪,但是,也并不是说每一种网络结构都有裁剪的空间,通常来说,应该结合实际情况,使用合适的技术手段,选择一个合适大小的模型文件。&/p&
&h2&14. TensorFlow API 的选择&/h2&
&p& TensorFlow 的 API 是很灵活的,也比较底层,在学习过程中发现,每个人写出来的代码,风格差异很大,而且很多工程师又采用了各种各样的技巧来简化代码,但是这其实反而在无形中又增加了代码的阅读难度,也不利于代码的复用。&/p&
&p& 第三方社区和 TensorFlow 官方,都意识到了这个问题,所以更好的做法是,使用封装度更高但又保持灵活性的 API 来进行开发。本文中的代码,就是使用 TensorFlow-Slim 编写的。&/p&
&h2&15. OpenCV 算法&/h2&
&p& 虽然用神经网络技术,已经得到了一个比 canny 算法更好的边缘检测效果,但是,神经网络也并不是万能的,干扰是仍然存在的,所以,第二个步骤中的数学模型算法,仍然是需要的,只不过因为第一个步骤中的边缘检测有了大幅度改善,所以第二个步骤中的算法,得到了适当的简化,而且算法整体的适应性也更强了。&/p&
&p& 这部分的算法如下图所示:&/p&
&img src=&/v2-fb6ee14c1cdeba81e9599f8ebebfff77_b.png& data-rawwidth=&1000& data-rawheight=&1333& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-fb6ee14c1cdeba81e9599f8ebebfff77_r.png&&&p& 按照编号顺序,几个关键步骤做了下面这些事情:&/p&
&ol&&li&用 HED 网络检测边缘,可以看到,这里得到的边缘线还是存在一些干扰的&/li&
&li&在前一步得到的图像上,使用 HoughLinesP 函数检测线段(蓝色线段)&/li&
&li&把前一步得到的线段延长成直线(绿色直线)&/li&
&li&在第二步中检测到的线段,有一些是很接近的,或者有些短线段是可以连接成一条更长的线段的,所以可以采用一些策略把它们合并到一起,这个时候,就要借助第三步中得到的直线。定义一种策略判断两条直线是否相等,当遇到相等的两条直线时,把这两条直线各自对应的线段再合并或连接成一条线段。这一步完成后,后面的步骤就只需要蓝色的线段而不需要绿色的直线了&/li&
&li&根据第四步得到的线段,计算它们之间的交叉点,临近的交叉点也可以合并,同时,把每一个交叉点和产生这个交叉点的线段也要关联在一起(每一个蓝色的点,都有一组红色的线段和它关联)&/li&
&li&对于第五步得到的所有交叉点,每次取出其中的 4 个,判断这 4 个点组成的四边形是否是一个合理的矩形(有透视变换效果的矩形),除了常规的判断策略,比如角度、边长的比值之外,还有一个判断条件就是每条边是否可以和第五步中得到的对应的点的关联线段重合,如果不能重合,则这个四边形就不太可能是我们期望检测出来的矩形&/li&
&li&经过第六步的过滤后,如果得到了多个四边形,可以再使用一个简单的过滤策略,比如排序找出周长或面积最大的矩形&/li&
&/ol&&p& 对于上面这个例子,第一版技术方案中检测出来的边缘线如下图所示:&/p&
&img src=&/v2-610d9f59c89ff0a6c8ec49_b.png& data-rawwidth=&1000& data-rawheight=&380& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/v2-610d9f59c89ff0a6c8ec49_r.png&&&p& 有兴趣的读者也可以考虑一下,在这种边缘图中,如何设计算法才能找出我们期望的那个矩形。&/p&
&h2&16. 总结&/h2&
&h4&算法角度&/h4&
&ul&&li&神经网络的参数/超参数的调优,通常只能基于经验来设置,有 magic trick 的成分&/li&
&li&神经网络/机器学习是一门试验科学&/li&
&li&对于监督学习,数据的标注成本很高,这一步很容易出现瓶颈&/li&
&li&论文、参考代码和自己的代码,这三者之间不完全一致也是正常现象&/li&
&li&对于某些需求,可以在模型的准确度、大小和运行速度之间找一个平衡点&/li&
&/ul&&h4&工程角度&/h4&
&ul&&li&end-to-end 网络无效的时候,可以用 pipeline 的思路考虑问题、拆分业务,针对性的使用神经网络技术&/li&
&li&至少要熟练掌握一种神经网络的开发框架,而且要追求代码的工程质量&/li&
&li&要掌握神经网络技术中的一些基本套路,举一反三&/li&
&li&要在学术界和工业界中间找平衡点,尽可能多的学习一些不同问题领域的神经网络模型,作为技术储备&/li&
&/ul&&h3&参考文献&/h3&
&a href=&/?target=http%3A//Hacker%27s%2520guide%2520to%2520Neural%2520Networks& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Hacker’s guide to Neural Networks&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//%25E9%E7%25A3%2585%25EF%25BC%%25A5%259E%25E7%25BB%258F%25E7%25BD%%25BB%259C%25E6%25B5%%25AE%25B2%25EF%25BC%259A%25E4%25BB%258E%25E7%25A5%259E%25E7%25BB%258F%25E5%E5%%25E6%25B7%25B1%25E5%25BA%25A6%25E5%25AD%25A6%25E4%25B9%25A0%OPEN%%25BC%%258F%%25BB%258F%25E9%25AA%258C%25E5%25BA%2593& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&神经网络浅讲:从神经元到深度学习&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//%25E5%E7%25B1%25BB%25E4%25B8%258E%25E5%259B%259E%25E5%25BD%%258C%25BA%25E5%2588%25AB%25E6%2598%25AF%25E4%25BB%%25B9%2588%25EF%25BC%259F%%25E7%259F%25A5%25E4%25B9%258E& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&分类与回归区别是什么?&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//%25E9%E7%25A3%7C%%25A5%259E%25E7%25BB%258F%25E7%25BD%%25BB%259C%25E6%259E%25B6%25E6%259E%%25BC%%25BF%259B%25E5%258F%25B2%25EF%25BC%259A%25E5%%25E9%259D%25A2%25E5%259B%259E%25E9%25A1%25BE%25E4%25BB%258ELeNet5%25E5%ENet%25E5%258D%%25BD%%25A7%258D%25E6%259E%25B6%25E6%259E%2584%25EF%25BC%%E8%25AE%25BA%25E6%EF%25BC%2589& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&神经网络架构演进史:全面回顾从LeNet5到ENet十余种架构&i class=&icon-external&&&/i&&/a&
&p&&a href=&/?target=http%3A//%25E6%%25E6%258D%25AE%25E7%259A%%25B8%25B8%25E6%EF%25BC%259A%25E5%%25E4%25B8%258E%25E7%2581%25AB%20%257C%%%%25A3%25B3%CoolShell& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&数据的游戏:冰与火&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//dataunion.org/20441.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&为什么“高大上”的算法工程师变成了数据民工?&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//Facebook%25E4%25BA%25BA%25E5%25B7%25A5%25E6%2599%25BA%25E8%2583%25BD%25E8%25B4%259F%25E8%25B4%25A3%25E4%25BA%25BAYann%2520LeCun%25E8%25B0%%25B7%25B1%25E5%25BA%25A6%25E5%25AD%25A6%25E4%25B9%25A0%25E7%259A%%25B1%%E6%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Facebook人工智能负责人Yann LeCun谈深度学习的局限性&i class=&icon-external&&&/i&&/a&
&p&&a href=&/?target=https%3A///technologymadeeasy/the-best-explanation-of-convolutional-neural-networks-on-the-internet-fbb8b1ad5df8%23.15uipz5fp& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The best explanation of Convolutional Neural Networks on the Internet!&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//%25E6%25B7%25B1%25E5%25BA%25A6%20%25E4%25BB%258E%25E5%%25E9%%25E5%%25E7%25B2%25BE%25E9%EF%25BC%259A%25E5%258D%25B7%25E7%25A7%25AF%25E7%25A5%259E%25E7%25BB%258F%25E7%25BD%%25BB%259C%25E5%E5%25AD%25A6%25E8%E6%258C%%258D%2597%25EF%25BC%%E8%25AE%25BA%25E6%EF%25BC%2589& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&从入门到精通:卷积神经网络初学者指南&i class=&icon-external&&&/i&&/a&&br&&a href=&/?target=http%3A//Transposed%2520Convolution%2C%2520Fractionally%2520Strided%2520Convolution%2520or%2520Deconvolution& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Transposed Convolution, Fractionally Strided Convolution or Deconvolution&i}

我要回帖

更多关于 spark yarn 提交任务 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信