机器学习把 Python 的历史地位推向了一个小高峰,我们习惯于用 Python 去编写机器学习的代码,但是在实际工程中,业务方用 Java 的可能性会比较大,那么如何用 Java 调用 Python 写的已经训练好的机器学习模型呢。本文接下来后以代码的形式极简演示,基于 Tensorflow 训练好的分类模型,如何使用 Java 应用调用。

很奇怪,对于这样的问题,网络上很少有正确的例子,或者说很少有基于 Tensorflow 高级分类器 DNNClassifier 导出的模型,用 Java 调用的例子,为了减少麻烦,如果要实验的情况下,请以下面环境为准。

  • Python: 3.6
  • Tensorflow: 1.10.0
  • Java 工程中的 gradle 依赖:
    compile 'org.tensorflow:tensorflow:1.10.0'
    compile 'org.tensorflow:proto:1.10.0'

Python 代码如下,以著名的 iris 代码为例,用 DNNClassifier 进行分类

# Created on: 2018/8/23. # Author: Heng Xiangzhong # May the code be with you! import pandas as pd import tensorflow as tf CSV_COLUMN_NAMES = [ 'SepalLength' , 'SepalWidth' , 'PetalLength' , 'PetalWidth' , 'Species' ] data_train = pd.read_csv( './data/iris_training.csv' , names=CSV_COLUMN_NAMES, header= 0 ) data_test = pd.read_csv( './data/iris_test.csv' , names=CSV_COLUMN_NAMES, header= 0 ) #print(data_train.head()) x_train = data_train y_train = data_train.pop( 'Species' ) x_test = data_test y_test = data_test.pop( 'Species' ) feature_columns = [] for key in x_train.keys(): feature_columns.append(tf.feature_column.numeric_column(key=key)) classifier = tf.estimator.DNNClassifier(feature_columns=feature_columns, hidden_units=[ 10 , 10 ], n_classes= 3 ) # 用来保存为 saved_model feature_spec = tf.feature_column.make_parse_example_spec(feature_columns) serving_input_receiver_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec) def train_func (x, y) : dataset = tf.data.Dataset.from_tensor_slices((dict(x), y)) dataset = dataset.shuffle( 1000 ).repeat().batch( 100 ) return dataset classifier.train(input_fn= lambda : train_func(x_train, y_train), steps= 1000 ) export_dir = classifier.export_savedmodel( ".\model" , serving_input_receiver_fn) print(export_dir) def eval_input_func (features, labels, batch_size) : features = dict(features) if labels is None : inputs = features else : inputs = (features, labels) dataset = tf.data.Dataset.from_tensor_slices(inputs) assert batch_size is not None , "batch_size must not be None" dataset = dataset.batch(batch_size) return dataset predict_arr = [] predictions = classifier.predict(input_fn= lambda : eval_input_func(x_test, labels=y_test, batch_size= 100 )) for predict in predictions: predict_arr.append(predict[ 'probabilities' ].argmax()) result = predict_arr == y_test result1 = [w for w in result if w == True ] print( "准确率为 %s" % str((len(result1)/len(result)))) # 如果想用 savedmodel 去加载,可以用下面的方法 def savedmodel_predict_demo () : with tf.Session(graph=tf.Graph()) as sess: predictor_func = tf.contrib.predictor.from_saved_model( ".\\model\\1535446427\\" ) feature = {} feature[ "SepalLength" ] = tf.train.Feature(float_list=tf.train.FloatList(value=[ 6.4 ])) feature[ "SepalWidth" ] = tf.train.Feature(float_list=tf.train.FloatList(value=[ 2.8 ])) feature[ "PetalLength" ] = tf.train.Feature(float_list=tf.train.FloatList(value=[ 5.6 ])) feature[ "PetalWidth" ] = tf.train.Feature(float_list=tf.train.FloatList(value=[ 2.2 ])) examples = [] example = tf.train.Example(features=tf.train.Features(feature=feature)) examples.append(example.SerializeToString()) result = predictor_func({ "inputs" : examples}) print(result)

其中比较重要的接口有,classifier.export_savedmodel, 它会保存模型为 pb 格式到相应目录,这种格式方便于在各种语言之间传播,适用于生产环境。代码片段中,savedmodel_predict_demo 函数演示了如何用 python 加载一个通过 savedmodel 保存的模型并作预测。
下文的 Java 代码演示了,用 Java 语言加载该模型并进行预测。

package org.henzox.test;
 * Created on: 2018/8/28.
 * Author:     Heng Xiangzhong
 * May the code be with you!
import org.tensorflow.SavedModelBundle;
import org.tensorflow.Session;
import org.tensorflow.Tensor;
import org.tensorflow.example.*;
public class LoadModelDemo {
    public static void main(String[] args) {
        Session session = SavedModelBundle.load(".\\model\\1535446427\\", "serve").session();
        Features.Builder builder = Features.newBuilder();
        builder.putFeature("SepalLength", Feature.newBuilder().setFloatList(FloatList.newBuilder().addValue(6.4f).build()).build());
        builder.putFeature("SepalWidth", Feature.newBuilder().setFloatList(FloatList.newBuilder().addValue(2.8f).build()).build());
        builder.putFeature("PetalLength", Feature.newBuilder().setFloatList(FloatList.newBuilder().addValue(5.6f).build()).build());
        builder.putFeature("PetalWidth", Feature.newBuilder().setFloatList(FloatList.newBuilder().addValue(2.2f).build()).build());
        Example.Builder exampleBuilder = Example.newBuilder().setFeatures(builder.build());
        byte[] str = exampleBuilder.build().toByteArray();
        byte[][] input = new byte[][]{str};
        Tensor<?> x = Tensor.create(input);
        float[][] y = session.runner().feed("input_example_tensor", x).fetch("dnn/head/predictions/probabilities").run().get(0).copyTo(new float[1][3]);

上面代码片段可对照用 python 方式加载模型的方法,其实是一样的步骤。值得注意的是在建立 Tensor 时,其实是字节数组,且最终给模型中的值都是经过 pb 序列化的。

代码中涉及到一些硬编码,比如 “input_example_tensor” 和 “dnn/head/predictions/probabilities” 这些字符串,它的由来可以通过
*** model\1535446427>saved_model_cli show --dir ./ --all
得到,输出为

MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['classification']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_STRING
        shape: (-1)
        name: input_example_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['classes'] tensor_info:
        dtype: DT_STRING
        shape: (-1, 3)
        name: dnn/head/Tile:0
    outputs['scores'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 3)
        name: dnn/head/predictions/probabilities:0
  Method name is: tensorflow/serving/classify
signature_def['predict']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['examples'] tensor_info:
        dtype: DT_STRING
        shape: (-1)
        name: input_example_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['class_ids'] tensor_info:
        dtype: DT_INT64
        shape: (-1, 1)
        name: dnn/head/predictions/ExpandDims:0
    outputs['classes'] tensor_info:
        dtype: DT_STRING
        shape: (-1, 1)
        name: dnn/head/predictions/str_classes:0
    outputs['logits'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 3)
        name: dnn/logits/BiasAdd:0
    outputs['probabilities'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 3)
        name: dnn/head/predictions/probabilities:0
  Method name is: tensorflow/serving/predict
signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_STRING
        shape: (-1)
        name: input_example_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['classes'] tensor_info:
        dtype: DT_STRING
        shape: (-1, 3)
        name: dnn/head/Tile:0
    outputs['scores'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 3)
        name: dnn/head/predictions/probabilities:0
  Method name is: tensorflow/serving/classify

我理解为,模型其实是一些方法声明,包括名称及调用约定,当实验报错时,可以沿着这些思路排查到底哪个地方出现了问题。
仅以此让大家少踩同样的坑。

1 特征工程 数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。 特征处理是特征工程的核心部分,sklearn提供了较为完整的特征处理方法,包括数据预处理,特征选择,降维等。首次接触到sklearn,通常会被其丰富且方便的算法模型库吸引,但是这里的介绍的特征处理库也十分强大! 2 数据预处理 通过特征提取,我们能得到未经处理的特征,这时的特征可能有以下问题: 不属于同一量纲:即特征的规格不一样,不能够放在一起比较。无量纲化可以解决这一问题。 信息冗余:对于某些定量特征,其包含的有效信 deepvac提供了PyTorch训练模型项目的工程化规范。项目地址:DeepVAC/deepvac​github.com欢迎star为了达到这一目标,deepvac包含了:项目组织规范代码规范deepvac库诸多PyTorch训练模型项目的内在逻辑都大同小异,因此deepvac致力于把更通用的逻辑剥离出来,从而使得工程代码的准确性、易读性、可维护性上更具优势。项目组织规范定义PyTorch训练模... Python 工程化这件事都没有统一的规范和项目管理的方案,也许是因为 Python 突然兴起的时间较短、用于开发大型项目的公司较少。所以为了帮助内部的同学更好的解决 Python 工程化问题和分享下个人的开发习惯和代码管理思路写下这篇文章。 一、基于opencv的dnn模块的调用。 笔者在1年多前的上一篇博客中已经详细讲过这部分。当时觉得opencv越来越强大了,但实际情况opencv也有它开发的局限性。后面我们会详细提到。opencv自从进去3.X的时代,新增了dnn模块,实现了对部分深度学习框架的支持。直到一周之前... @深度学习的工程化应用 作为一名深度学习的初学者,很高兴能与大家分享一下我的学习心得,首先说一下我进行深度学习的目的:将深度学习网络模型工程化。展开说就是用不同的开发工具(如:C/C++、C#、Java等)调用你训练好的网络模型,进而将其工程化。 深度学习平台搭建 本人笔记本配置:9th CORE i7处理器(CPU),GTX1060TI(显卡),内存16G(RAM),WIN10(操作系统) 在整个的开发过过程中我们所需要的相关软件(以下版本为我所使用完美运行版本,仅供参考): Pycharm GBDT+LR简介 前面介绍的协同过滤和矩阵分解存在的劣势就是仅利用了用户与物品相互行为信息进行推荐, 忽视了用户自身特征, 物品自身特征以及上下文信息等,导致生成的结果往往会比较片面。 而这次介绍的这个模型是2014年由Facebook提出的GBDT+LR模型, 该模型利用GBDT自动进行特征筛选和组合, 进而生成新的离散特征向量, 再把该特征向量当做LR模型的输入, 来产生最后的预测结果, 该模型能够综合利用用户、物品和上下文等多种不同的特征, 生成较为全面的推荐结果, 在CTR点击率预估场景下使用较. 今天我就来聊聊的对于模型的线上部署和工程化的问题的认识和工程实践。在企业中,我们做模型的目的就是为了能够让它来更好的解决产品在实际生产过程中所遇到的具体的问题,而模型训练好之后,下一步要... ML:从工程化思维分析—机器学习团队十大角色的简介(背景/职责/产出物):产品经理、项目经理、业务咨询顾问、数据科学家、ML研究员、数据工程师、ML工程师、DevOps/软件开发/交付工程师 机器学习团队十大角色背景、职责、产出物划分 目的: 更准确高效的选择改进方法,提高模型搭建效率  实际训练模型时可能遇到各种情况,对应各种解决方法,若没有一个清晰统一的指导路线,就会手忙脚乱不知所措,进行大量无需尝试 1.2 正交化(Orthogonalization) 定义: 正交化或正交性是一种系统设计属性。尽可能将每次修改的影响相互独立,一次修改只影响一个部分,不会相互影响,这样就能更容易的判断问题所在,减少模型搭建时间。 例子: 直观理解  电视:每个旋钮只控制一件事,如果声音太小了,只 tf.data.Dataset.from_tensor_slices 该函数是dataset核心函数之一,它的作用是把给定的元组、列表和张量等数据进行特征切片。切片的范围是从最外层维度开始的。如果有多个特征进行组合,那么一次切片是把每个组合的最外维度的数据切开,分成一组一组的。 假设我们现在有两组数据,分别是特征和标签,为了简化说明问题,我们假设每两个特征对应一个标签。之后把特征和标签组合成一个... 文章目录笔记业务流程迭代中的模型改进1.损失函数的选择2.业务规则融入模型3.缺失值处理长尾问题优化工程开发实践1.训练实践部分整体训练流程数据并行训练方式TF模型集成预处理2.TF模型线上预测 今天在美团技术博客上学习了一下送达时间的预测模型工程化,记录一下。 ETA(Estimated Time of Arrival,“预计送达时间”),即用户下单后,配送人员在多长时间内将外卖送达到用户... deepvac提供了PyTorch训练模型项目的工程化规范。项目地址:DeepVAC/deepvac​github.com欢迎star为了达到这一目标,deepvac包含了:项目组织规范代码规范deepvac库诸多PyTorch训练模型项目的内在逻辑都大同小异,因此deepvac致力于把更通用的逻辑剥离出来,从而使得工程代码的准确性、易读性、可维护性上更具优势。项目组织规范定义PyTorch训练模... 文章目录什么是IDEF1X独立实体和从属实体标定联系和非标定联系非确定联系与相交实体分类联系与分类实体 什么是IDEF1X IDEF1X 是 E-R 图的一种细化。例如实体被细分为独立实体、从属实体;其它概念也有所细化。 可以将 IDEF1X 看作是除了 Chen 和 Crow’s foot 之外的第三种表达方法。 独立实体和从属实体 独立实体又称 强实体,从属实体又称 弱实体。 独立实体 的实例的唯一标识不依赖于它与其它实体的联系。主关键字没有外键。 从属实体 的实例的唯一标识依赖于这个实体与其它实体的联       有同事说,为什么HW的软件做这么好?因为任正非是学建筑的。先不讨论这是不是真的有这个原因。单纯的从建筑/土木工程和软件来对比一下。      建筑/土木工程:没有任何容错的余地,必须先计算、仿真,做到万无一失,再去实施,设计阶段比实施阶段花的时间要长、设计阶段的工程师水平也往往是业内顶级的工程师,是建筑/土木行业金字塔尖上的那部分人。实施过程中还要有监控公司专门做质量控制。并且需要符合... caffe团队用imagenet图片进行训练,迭代30多万次,训练出来一个model。这个model将图片分为1000类,应该是目前为止最好的图片分类model了。 假设我现在有一些自己的图片想进行分类,但样本量太小,可能只有几百张,而一般深度学习都要求样本量在1万以上,因此训练出来的model精度太低,根本用不上,那怎么办呢? 那就用caffe团队提供给我们的model吧。 因为训练好的m...