【个人总结】sentiment-analysis inference从后台到前端

最近因为项目需要,需要实现把TensorFlow基于Text CNN的情感分析的inference从后台服务器转移到前端,也就是说在客户端进行深度学习推断。
使用的框架是Google的TensorFlow.js,一个用JavaScript编写的前端深度学习框架。不得不说,Google的工程师真是才华横溢,能在前端实现如此优雅的框架,并提供了许多有用的文档、工具和支持。然而,这个过程不仅不顺利而且异常坎坷,一路上踩了无数的坑。
一上来采取的路线并不是通过前端库重构Python代码,一方面是因为对TensorFlow.js并不熟悉,另一方面担心很多Python中实现的功能在JavaScript上并没有实现,导致模型无法很好的翻译到上面去。
不过官方提供了一个工具,可以将用TensorFlow或者Keras构建和保存的模型转换成前端能够识别和加载的json模型,这个工具仍在不断更新中,并且已经兼容了最新的TensorFlow2.0,可以说工程师们真的很有效率了。转换器地址如下:https://github.com/tensorflow/tfjs-converter
情感分析最初的代码是用TensorFlow写的,转换器给的推荐方法是用saved model,但不知为什么我在转换过程中一直报错,缺少各种函数,现在想想应该是TensorFlow版本与其并不是很兼容,至于这个版本问题是我踩的一个大坑,后面再说。
我注意到,转换器的readme中说如果采用的是Frozen Model需要将转换器的版本降级到0.8,照做以后,发现还是报错,是由于命令行参数格式错误,当然readme里并没有仔细介绍这0.8版本的模型该怎么输入命令行参数,参考了一下程序中的格式说明和网上一些教程才调对了。
然后,问题又来了,模型是转换出来了,本来以为大功告成,结果loadFrozenModel时又报了新的错误,getIndices函数没有被重载,然后我就开始漫长了Baidu、Bing、Google、Stack Overflow之旅,搜遍了能搜到的所有结果,仍然没有对应的,包括后来,我逐渐发现很多奇葩的错误真的都是个例,你可能就是第一个遇到的人,毕竟我基本可以相信没几个人尝试过把我拥有的这段中文情感分析代码转到前端。
为了这个问题,我付出了整整一天,搜了能搜的所有结果,最后在Stack Overflow上提问,然后把我能找到跟TensorFlow.js这个项目的所有工程师邮箱都发了一封求助信。我只记得那天特别心累,感觉除了等回复已经没有办法了。
另外,在搜索的过程中,我深深的感受到Bing国际版、Google查询程序错误与编程相关知识的优势,结果比中文搜索引擎搜到的东西多了很多,而且很多都一针见血,尤其是Stack Overflow,这个平台的问题推送与用户综合水平都较高,而且很有一种不解决Bug不罢休的骇客精神,不是特别确定绝不会回答,所以一般回答数比较少,当然门槛也要高一点,比如作为新用户的我,点赞的权限都不够。
我们的问题贴在Stack Overflow上久久没有人回答,毕竟我的情况太个例了,问题也太具体了。我就这样等了三四天,指导有一天发现有人提示我的tfjs库版本过低。我仿佛发现了新大陆,立刻更改版本,却发现loadFrozenModel在最新版本已经被删除了,相应的是loadGraphModel,需要的模型格式是model.json,与我用0.8版本输出的并不一样。所以我不得不升级我的转换器重新转换,绕了很多弯子发现还不如直接去看readme,写的准确又清晰,没必要搜什么博客。
转换过程又花了我半天,因为像当初一样,又是在报错,我发现是一个import错误,我去TensorFlow的文件夹里查了一下,果然没有那个文件,莫非是我的TensorFlow版本有问题?我升级到1.12.0,仍然有问题,然后又升级到最新的1.13.1,还是在报错。我觉得很奇怪,去Tensorflow的GitHub仓库搜索了一下,有这个文件,难道我升级失败了?后来想想可能是和我用的清华源有关系,总之和GitHub的最新代码不同。
这期间Google的一个工程师回复我了,但是并没有给出什么有效的帮助,后续的求助信就没有回复过了。
我从GitHub上下载下最新的TensorFlow代码,当然因为不是安装包,还是有一些问题,把没有的文件复制进去,import失败什么我就复制进去什么,以为快要成功时又报了另外一个奇怪的未定义错误,这个已经不是我复制py文件可以解决的了。我更加确信我应该正确的获取这段代码才能解决,最后我发现TensorFlow还有预览版,安装后,那些错误不再出现。到此为止,我已经重装了约20遍TensorFlow。
但是转换器依然不能正确的运行,比如下面的错误:
“tensorflow.python.eager.lift_to_graph.UnliftableError: Unable to lift tensor <tf.Tensor ‘IdentityN:0’ shape=(?,) dtype=int64> because it depends transitively on placeholder <tf.Operation ‘dropout_keep_prob’ type=Placeholder> via at least one path, e.g.: IdentityN (IdentityN) <- scores (BiasAdd) <- scores/MatMul (MatMul) <- dropout/dropout/mul (Mul) <- dropout/dropout/Floor (Floor) <- dropout/dropout/add (Add) <- dropout_keep_prob (Placeholder)”
或者:
"2019-03-20 23:07:05.970985: I tensorflow/core/grappler/devices.cc:53] Number of eligible GPUs (core count >= 8): 0 (Note: TensorFlow was not compiled with CUDA support) 2019-03-20 23:07:05.978764: I tensorflow/core/grappler/clusters/single_machine.cc:359] Starting new session 2019-03-20 23:07:05.985340: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 2019-03-20 23:07:06.072370: E tensorflow/core/grappler/grappler_item_builder.cc:636] Init node Variable/Assign doesn’t exist in graph Traceback (most recent call last): File “d:\anaconda3\lib\site-packages\tensorflow\python\grappler\tf_optimizer.py”, line 43, in OptimizeGraph verbose, graph_id, status) SystemError: returned NULL without setting an error

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File “d:\anaconda3\lib\runpy.py”, line 193, in run_module_as_main “main”, mod_spec) File “d:\anaconda3\lib\runpy.py”, line 85, in run_code exec(code, run_globals) File "D:\Anaconda3\Scripts\tensorflowjs_converter.exe__main.py", line 9, in File “d:\anaconda3\lib\site-packages\tensorflowjs\converters\converter.py”, line 358, in main strip_debug_ops=FLAGS.strip_debug_ops) File “d:\anaconda3\lib\site-packages\tensorflowjs\converters\tf_saved_model_conversion_v2.py”, line 271, in convert_tf_saved_model concrete_func) File “d:\anaconda3\lib\site-packages\tensorflow\python\framework\convert_to_constants.py”, line 140, in convert_variables_to_constants_v2 graph_def = _run_inline_graph_optimization(func) File “d:\anaconda3\lib\site-packages\tensorflow\python\framework\convert_to_constants.py”, line 59, in _run_inline_graph_optimization return tf_optimizer.OptimizeGraph(config, meta_graph) File “d:\anaconda3\lib\site-packages\tensorflow\python\grappler\tf_optimizer.py”, line 43, in OptimizeGraph verbose, graph_id, status) File “d:\anaconda3\lib\site-packages\tensorflow\python\framework\errors_impl.py”, line 548, in exit c_api.TF_GetCode(self.status.status)) tensorflow.python.framework.errors_impl.InvalidArgumentError: Failed to import metagraph, check error log for more info."
我开始认为是我的模型结构有问题或者是Saved Model参数没有输入好,我便开始改参数,改模型,但是怎么也输出不了正确的解决,我搜了各种Saved Model输出的用法都在报一样的错误。我测试了一个简单的TensorFlow模型,也无法正确转换,发现不是模型的问题,而是转换过程或者模型存储的问题。我继续在Stack Overflow上提问,不过因为问题个性化太强,也没有什么答案。折腾了很久,最后我放弃了。我最后也没搞出来如何将Saved Model转换成model.json。
我的折腾之旅远没有结束。
我仍然不死心,想试试Keras模型,这需要做的就是在Keras上实现这个TextCNN,GitHub上示例代码也不少,我比着TensorFlow代码,期间也遇到很多“翻译问题”,比如Keras中间不能断层,通过Lambda层解决了,然而我并不知道这个Lambda层是我接下来问题的一个根源。大约用了一个中午和一个下午,Keras代码成功写好,然后训了的20个Epoch,并研究了一下Keras如何每个Epoch保存一个模型,是通过回调实现的,但是保存一直不成功,尝试了很久还是不成功,便去Stack Overflow上提问了,这次回复异常的快,而且瞬间解决了问题,不得不说Stack Overflow上高手还是挺多的,而且很有耐心的看完我的代码。最终得到了model.h5。
放到转化器里,改了改参数,没有任何提示,文件夹里已经出现了model.json。
你以为这就成功了?其实还差得远,折腾完这个半天后我又接着折腾了一整天。
首先我发现模型加载出错,有个函数未定义。难到又是框架版本的问题?我用的明明是最新版本。我随后在GitHub的issues上提问,第二天早晨发现已经有工程师回复了我。原来是我用错加载方法了,应该使用loadLayerModel而不是TensorFlow的loadGraphModel,readme上写的也很清楚,是我大意了,赶紧改过来,加载成功了。
然而又出现了新的错误,在Python代码里我写了个eval无法运行,是因为Lambda层有问题,需要用特殊的方法声明。但是在js里加载模型的时候也提示Lambda层有问题,查了很久资料在GitHub的issues区域发现也有人遇到这个问题,因为这个层涉及到Python的一些用法,js代码里并没有实现这个层,官方也表示不会实现。但是没有Lambda我用到的一个expand_dim函数就不能用了,输入模型的张量维度也会出现问题,难到真的没有什么东西可以替代吗?我查了一两个小时的资料,感觉是没有。在我快放弃的时候,发现了Reshape层,虽然有点担心也没有实现,但是还是硬着头皮把维度调整好,再次训练和转换模型,模型加载成功了。
当然,这还是没有成功,预测时模型提示什么symbolic output是null,我百思不得其解,后来想这个函数会不会也用错了呢?果然,我查了官方文档后发现应该用另外一个函数。改过来以后又出现了新的问题。所以我发现认真看看官方文档有时候比乱搜什么博客效果好的多。
这次的问题是输入维度和模型要求的维度不同,我觉得很奇怪,测试一会儿发现这个输入向量的维度甚至还在变。我仔细研究了一下数据处理的函数,发现了原因,这个维度是根据训练时外部的一个vec文件的最长序列而定的,而这个文件在操作的时候会被写入,也就是说这个文件和之前训练的模型不匹配了。所以无论是后台eval还是前端predict都会因为与文件里的长度不符,发现了这个问题后我重新训练了模型,并对相关外部文件小心对待,得到了最终的模型后,前端输出了预测值。对输入的来源和维度,以及输出的数据稍加处理便可以正常使用,最后只要把它部署到项目就可以了。
终于,我实现了将情感分析从后台inference转移到了前端。
很多坑和问题没有完全列出,大体踩坑的故事就是这样,为了实现这个,大约付出了五整天,很多次都想放弃了。如果要总结点什么的话,就是认真看官方文档,好好利用Stack Overflow与GitHub,尽量用国际搜索引擎,分析问题时要有耐心,敏锐发现问题之所在,实在解决不了去社区提问或者给官方发邮件,有的时候可能问题就是代码的细节比如版本兼容问题或者某个变量名,另外一条路走不通要及时放弃寻找新的方法,毕竟,条条大路通罗马。

发布了75 篇原创文章 · 获赞 28 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Swocky/article/details/88751051
今日推荐