1. 现实的困境
数字化时代,众多的商业项目都具备了AI的属性。短视频推荐、景区人流量预测等各种应用的背后是一个个运行了AI算法的系统在提供支撑。
各个企业中,开发上述这些AI系统的工程师通常会划分为以下团队,前端团队,后端团队,算法团队。这样的工种划分传递到了校园招聘中,甚至传导到了高校的学生队伍中。从学生时代就会区分,某同学志在将来要从事应用系统开发,那么他就会有意无意的离算法远一些。立志从事算法研究的同学又会疏于应用系统开发所需繁杂知识的积累。学生毕业进入企业后,由于工种的划分,应用与算法的鸿沟进一步被拉大。
如果是纯算法研究项目或者研发一些演示项目,这样的划分并无不可。但是对于商用项目,这样的划分会导致很多问题:
1)后端研发人员不清楚算法实现逻辑,将所有涉及数值计算的特性都划分到算法,系统的应用微服务和算法微服务之间调用频繁,增加了业务的复杂度。后端研发人员没有深入业务,对系统的认识较为肤浅,个人成长也受限。
2)算法研发人员只关注算法功能的实现,对于算法在商用环境下的健壮性,可维护性考虑甚少。
3)java语言是强类型语言,很多问题在开发阶段就会暴露出来。同时,java语言具备强大的生态,各种代码静态检查工具能够检测出很多潜在的缺陷;各种开源的库几乎能解决你所有遇到的问题。
相比java语言,python语言的语法较为简洁,在机器学习和深度学习方面的生态更是java语言所无法比拟。但是,由于其本身的弱类型特征,很多问题在运行阶段才会暴露出来,导致系统的稳定性和安全性较差。
对于一款商业软件,软件开发完成部署上线的时间在整个软件生命周期中只占很小的比重。因此软件架构要做到高内聚、低耦合。选择的开发语言/框架要易于理解、生态完善、易于维护。
2. 你永远不是第一个遇到问题的人
在软件开发领域,始终要相信你永远不是第一个遇到问题的人。困扰你的问题也会困扰别人(除非你的项目特别偏门),或许别人已经有了比较好的解决方案。
回到AI系统,在AI开发领域AWS构建了Deep Java Library (DJL),一个为 Java 开发者定制的开源深度学习框架。看下官网的介绍:
很长时间以来,Java 一直是一个很受企业欢迎的编程语言。得益于丰富的生态以及完善维护的包和框架,Java 拥有着庞大的开发者社区。尽管深度学习应用的不断演进和落地,提供给Java开发者的框架和库却十分短缺。现今主要流行的深度学习模型都是用 python 编译和训练的。对于 Java 开发者而言,如果要进军深度学习界,就需要重新学习并接受一门新的编程语言同时还要学习深度学习的复杂知识。这使得大部分Java开发者学习和转型深度学习开发变得困难重重。
为了减少 Java 开发者学习深度学习的成本,AWS构建了Deep Java Library (DJL),一个为 Java 开发者定制的开源深度学习框架。它为 Java 开发者对接主流深度学习框架提供了一个桥梁。DJL 同时对 Apache MXNet,PyTorch 和 TensorFlow 最新版本的支持,使得开发者可以轻松使用Java构建训练和推理任务。
3. DJL和Tablesaw
DJL设计的目标是在既有的深度学习框架之上提供了一套基于Java的API,屏蔽各种深度学习框架TensorFlow、PyTorch、Mxnet的差异,降低java开发者的学习成本。
看下官方example中目标检测的一段代码(看不懂没关系,看下大概的API就好):
public static DetectedObjects predict() throws IOException, ModelException, TranslateException {
Path imageFile = Paths.get("src/test/resources/dog_bike_car.jpg");
Image img = ImageFactory.getInstance().fromFile(imageFile);
String modelUrl =
"http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_320x320_coco17_tpu-8.tar.gz";
Criteria<Image, DetectedObjects> criteria =
Criteria.builder()
.optApplication(Application.CV.OBJECT_DETECTION)
.setTypes(Image.class, DetectedObjects.class)
.optModelUrls(modelUrl)
// saved_model.pb file is in the subfolder of the model archive file
.optModelName("ssd_mobilenet_v2_320x320_coco17_tpu-8/saved_model")
.optTranslator(new MyTranslator())
.optProgress(new ProgressBar())
.build();
try (ZooModel<Image, DetectedObjects> model = ModelZoo.loadModel(criteria);
Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
DetectedObjects detection = predictor.predict(img);
saveBoundingBoxImage(img, detection);
return detection;
}
}
在数值计算方面,DJL提供了NDArray。使用NDArray,用和Numpy同样简单的语法,达到同样的效果。
例如:
使用numpy,
nd = np.ones((2, 3))
/*
[[1. 1. 1.]
[1. 1. 1.]]
*/
使用DJL提供的NDArray,
NDArray nd = manager.ones(new Shape(2, 3));
/*
ND: (2, 3) cpu() float32
[[1., 1., 1.],
[1., 1., 1.],
]
*/
很遗憾,DJL还没有提供python数值计算库pandas类似的API。但是,你永远不是第一个遇到问题的人,java语言的pandas已经有开源实现了。那就是Tablesaw库。(还有其他的库,Tablesaw较优)
创建Table(类比pandas中的dataframe),
String[] animals = {"bear", "cat", "giraffe"};
double[] cuteness = {90.1, 84.3, 99.7};
Table cuteAnimals =
Table.create("Cute Animals")
.addColumns(
StringColumn.create("Animal types", animals),
DoubleColumn.create("rating", cuteness));
导入文件:
Table bushTable = Table.read().csv("../data/bush.csv");
数据探索:
System.out.println(bushTable.structure())
Structure of bush.csv
Index | Column Name | Column Type |
-----------------------------------------
0 | date | LOCAL_DATE |
1 | approval | INTEGER |
2 | who | STRING |
4.落地建议
项目中最近遇到的一个问题,后台程序需要对用户输入的一个文件进行分析计算,分别按照按15分钟和小时粒度汇总,然后计算得出各个粒度的阈值。算法开发人员已经用python代码实现了这个算法。解决这个问题有以下方案:
1)实现一个python服务,封装该算法。
这种方案的问题在于多了一个服务,多了一个服务要考虑的事情非常多。多实例,高可靠,日志等等。(其实这是典型的AWS Lambda的应用场景)
2)后台java服务用Spark提交计算任务
这种方案更复杂,为了调用一个python算法,部署一套spark环境,性价比太低。
3)java程序本地调用python代码
这种方案较为简单,但是需要java程序运行环境中同时具备python脚本的运行环境。docker基础镜像的体积增大不说,也提高了运维的复杂度。
4)使用Tablesaw库实现算法。
这种方案几乎没什么缺点,代价就是java程序员要把python算法再实现一遍。如果算法不是十分复杂,这个难度也不大。
5.项目边界划分
当前的项目边界划分,只要涉及略微复杂的数值计算都用python算法实现。
后续DJL框架逐步稳定后,可以按照如下的界限划分:
甚至部分模型训练的工作也可以放在java服务中。
6.人员能力终究有限
很多人可能会想,如果算法程序员开发的算法健壮性足够高,是否上述的问题都不存在了?这种想法可能有点理想化:
1)优秀的程序员很少,所以要借助各种语言特性保证我们的代码质量,否则世界上只有一门C语言就够了;
2)工程实践和工程素养都需要时间磨练
或许,DJL框架对于只写增删改查逻辑的程序员还是有一些门槛。一方面DJL在不断地降低门槛,另一方面很多时候难度来自自我设限。程序员群体基本都是大学本科以上学历,掌握API层面的AI知识还是能够做到的。况且,做电商软件的java程序员要学习电商业务,做财务软件的java程序员要学习财务知识,做AI的java程序员为何不能学习AI领域的知识呢?
对于springboot项目,aws提供了配置自动配置环境(底层算法引擎)和寻找模型
<dependency>
<groupId>ai.djl.spring</groupId>
<artifactId>djl-spring-boot-starter-autoconfigure</artifactId>
<version>${djl.starter.version}</version>
</dependency>
同时,在jupyter notebook中也提供了入口(基于JDK9+版本的JShell技术实现,让我头一次认识到了JShell的价值所在):
参考资料:
1.https://github.com/deepjavalibrary/djl(Deep Java Library (DJL))
2.https://djl.ai/website/demo.html(DJL Block Runner)
3.https://aws.amazon.com/cn/blogs/china/start-training-deep-learning-models-with-java/(动手用 Java 训练深度学习模型)
4.https://github.com/deepjavalibrary/djl/blob/master/jupyter/README.md(安装jupyter-java)
5.https://jtablesaw.github.io/tablesaw/gettingstarted(Java dataframe and visualization library)