大家好! 今天,在构建ML模型中,Python处于领导地位,并在数据科学专家社区中广受欢迎[
1 ]。
像大多数开发人员一样,Python的简洁和简洁的语法吸引了我们。 我们使用它来使用人工神经网络解决机器学习问题。 但是,实际上,产品开发语言并不总是使用Python,这需要我们解决其他集成问题。
在本文中,我将讨论当需要将Python的Keras模型与Java关联时所使用的解决方案。
我们要注意的是:
- 功能捆绑Keras模型和Java;
- 准备与DeepLearning4j框架(简称DL4J)一起使用;
- 将Keras模型导入DL4J(请注意,本节包含了许多见解)-如何注册图层,导入模块的局限性,如何检查工作结果。
为什么要阅读?
- 为了节省启动时间,如果您将面临类似集成的任务;
- 找出我们的解决方案是否适合您,以及您是否可以重用我们的经验。
深度学习框架重要性的整体特征[
2 ]。
最受欢迎的深度学习框架的摘要可在此处[
3 ]和此处[
4 ]中找到。
如您所见,这些框架中的大多数都基于Python和C ++:它们使用C ++作为内核来加速基本和高负荷操作,并使用Python作为交互接口来加速开发。
实际上,许多开发语言都更加广泛。 Java是大型企业和组织产品开发的领导者。 一些流行的神经网络框架具有JNI / JNA绑定程序形式的Java端口,但是在这种情况下,需要为每种体系结构构建一个项目,并且需要Java在跨平台模糊问题上的优势。 这种细微差别在复制解决方案中可能非常重要。
另一种替代方法是使用Jython编译为Java字节码。 但这里有一个缺点-仅支持Python的第二版本,以及使用第三方Python库的能力有限。
为了简化Java中神经网络解决方案的开发,正在开发DeepLearning4j框架(简称DL4J)。 DL4除了Java API外,还提供了一组预训练的模型[
5 ]。 通常,此开发工具很难与TensorFlow竞争。 TensorFlow在更详细的文档和大量示例,技术能力,社区规模和快速开发方面胜过DL4J。 尽管如此,Skymind坚持的趋势还是很有希望的。 Java中尚未发现该工具的重要竞争对手。
DL4J库是少数几个(如果不是唯一的)库,可以导入Keras模型;它在功能上扩展了Keras熟悉的层[
6 ]。 DL4J库包含一个目录,其中包含神经网络ML模型的实现示例(dl4j-example)。 在我们的案例中,用Java实现这些模型的精妙之处并不那么有趣。 将更加详细地关注使用DL4J方法将经过训练的Keras / TF模型导入Java。
开始使用
在开始之前,您需要安装必要的程序:
- Java版本1.7(64位版本)及更高版本。
- Apache Maven项目构建系统。
- IDE可供选择:Intellij IDEA,Eclipse,Netbeans。 开发人员建议使用第一个选项,此外,还将讨论可用的培训示例。
- Git(用于将项目克隆到您的PC)。
在此处[
7 ]或在视频[
8 ]中可以找到带有启动示例的详细说明。
要导入模型,DL4J开发人员建议使用
KerasModelImport导入
模块 (于2016年10月出现)。 模块的功能支持Keras的两种模型架构-它是顺序的(Java中的模拟-类MultiLayerNetwork)和功能性(Java中的模拟-类ComputationGraph)。 该模型将以HDF5格式整体导入或导入2个单独的文件-具有h5扩展名的模型权重和包含神经网络体系结构的json文件。
为了快速起步,DL4J开发人员针对类型为Sequential [
9 ]的模型在Fisher Fisher数据集上准备了一个简单示例的分步分析。 从以两种方式导入模型的角度考虑了另一个培训示例(1:以完整的HDF5格式; 2:在单独的文件中-模型权重(h5扩展)和体系结构(json扩展)),然后比较Python和Java模型的结果[
10 ]。 到此结束了对导入模块实用功能的讨论。
Java中也有TF,但是它处于实验状态,开发人员没有对其稳定操作的任何保证[
11 ]。 版本存在问题,并且Java中的TF具有不完整的API-这就是为什么此处不考虑此选项的原因。
原始Keras / TF模型的功能:
导入神经网络非常简单。 在代码中,我们将更详细地分析将神经网络与更复杂的体系结构集成的示例。
您不应该讨论此模型的实际方面,它是从考虑层(尤其是Lambda层的注册),导入模块的某些细微之处和局限性以及整个DL4J的角度进行指示的。 实际上,提到的细微差别可能需要调整网络体系结构,或者完全放弃通过DL4J启动模型的方法。
型号特点:
1.模型类型-功能(具有分支的网络);
2.选择小的训练参数(批的大小,时代数):批的大小-100,时代数-10,每个时代的步长-10;
3. 13层,各层的摘要如图所示:
简短说明- input_1-输入层,接受二维张量(由矩阵表示);
- lambda_1-在我们的示例中,用户层使张量在TF中的填充具有相同的数值;
- embedding_1-为文本数据的输入序列构建嵌入(矢量表示)(将2-D张量转换为3-D);
- conv1d_1-一维卷积层;
- lstm_2-LSTM层(在embed_1(第3号)层之后进行);
- lstm_1-LSTM层(在conv1d(第4号)层之后);
- lambda_2是在lstm_2(第5号)层之后截断张量的用户层(与lambda_1(第2号)层的填充相反的操作);
- lambda_3是在lstm_1(第6号)和conv1d_1(第4号)层之后张量被截断的用户层(与lambda_1(第2号)层中的填充相反的操作);
- concatenate_1-截断的(No. 7)和(No. 8)层的粘合;
- density_1-8个神经元的完全连接层和指数线性激活函数“ elu”;
- batch_normalization_1-标准化层;
- density_2-1个神经元和乙状结肠激活功能“乙状结肠”的完全连接层;
- lambda_4-用户层,在该用户层执行前一层的压缩(TF中的压缩)。
4.损失函数-binary_crossentropy
5.模型质量度量-谐波平均值(F度量)
在我们的案例中,质量指标的问题不如进口的正确性重要。 导入的正确性取决于在推理模式下工作的Python和Java NN模型中结果的重合性。
在DL4J中导入Keras模型:
使用的版本:Tensorflow 1.5.0和Keras 2.2.5。 在我们的案例中,HDF5文件将Python模型整体上载。
将模型导入DL4J时,导入模块不提供用于传递其他参数的API方法:张量流模块的名称(构建模型时从中导入函数)。
一般来说,DL4J仅与Keras函数一起使用,在Keras导入部分[
6 ]中给出了详尽的列表,因此,如果使用TF的方法在Keras上创建了模型(如本例所示),则导入模块将无法识别它们。
导入模型的一般准则
显然,使用Keras模型意味着需要对其进行反复训练。 为此,为节省时间,设置了训练参数(1个时期),每个时期设置了1步(steps_per_epoch)。
首次导入模型时,尤其是具有唯一的自定义层和稀有层组合的模型时,成功的可能性不大。 因此,建议以迭代方式执行导入过程:减少Keras模型的层数,直到您可以在Java中导入并运行该模型而没有错误为止。 接下来,一次向Keras模型添加一层,然后将结果模型导入Java,以解决发生的错误。
使用TF损失功能
为了证明,当导入Java时,训练模型的损失函数必须来自Keras,我们使用了Tensorflow中的log_loss(与custom_loss函数最相似)。 我们在控制台中收到以下错误:
Exception in thread "main" org.deeplearning4j.nn.modelimport.keras.exceptions.UnsupportedKerasConfigurationException: Unknown Keras loss function log_loss.
用Keras替换TF方法
在我们的案例中,TF模块中的函数使用了2次,在所有情况下,它们仅在lambda层中找到。
Lambda图层是用于添加任意功能的自定义图层。
我们的模型只有4个lambda层。 事实是,在Java中,必须通过KerasLayer.registerLambdaLayer手动注册这些lambda层(否则,我们会收到错误[
12 ])。 在这种情况下,在lambda层内部定义的函数应该是来自相应Java库的函数。 在Java中,没有注册这些层的示例,也没有为此提供全面的文档。 一个例子在这里[
13 ]。 从示例[
14,15 ]借来了一般考虑。
依次考虑在Java中注册模型的所有lambda层:
1)Lambda层,用于沿给定方向(在本例中为左右)将有限量的常数添加到张量(矩阵):
该层的输入连接到模型的输入。
1.1)Python层:
padding = keras.layers.Lambda(lambda x: tf.pad(x, paddings=[[0, 0], [10, 10]], constant_values=1))(embedding)
为了清楚起见,该层的功能正常工作,我们在python层中显式替换了数值。
1.2)Java层:
KerasLayer.registerLambdaLayer("lambda_1", new SameDiffLambdaLayer() { @Override public SDVariable defineLayer(SameDiff sameDiff, SDVariable sdVariable) { return sameDiff.nn().pad(sdVariable, new int[][]{ { 0, 0 }, { 10, 10 }}, 1); } @Override public InputType getOutputType(int layerIndex, InputType inputType) { return InputType.feedForward(20); } });
在Java中所有已注册的lambda层中,重新定义了2个函数:
第一个函数“ definelayer”负责所使用的方法(一点也不明显:该方法只能在nn()后端以下使用); getOutputType负责注册层的输出,该参数是一个数字参数(此处为20,但通常允许使用任何整数值)。 它看起来不一致,但它的工作原理是这样的。
2)Lambda层,用于沿给定方向(在我们的示例中为左右)修剪张量(矩阵):
在这种情况下,LSTM层进入lambda层的输入。
2.1)Python层:
slicing_lstm = keras.layers.Lambda(lambda x: x[:, 10:-10])(lstm)
2.2)Java层:
KerasLayer.registerLambdaLayer("lambda_2", new SameDiffLambdaLayer() { @Override public SDVariable defineLayer(SameDiff sameDiff, SDVariable sdVariable) { return sameDiff.stridedSlice(sdVariable, new int[]{ 0, 0, 10 }, new int[]{ (int)sdVariable.getShape()[0], (int)sdVariable.getShape()[1], (int)sdVariable.getShape()[2]-10}, new int[]{ 1, 1, 1 }); } @Override public InputType getOutputType(int layerIndex, InputType inputType) { return InputType.recurrent(60); } });
对于此层,InputType参数从前馈(20)更改为递归(60)。 在递归参数中,数字可以是任何整数(非零),但其与下一个lambda层的递归参数的总和应为160(即在下一层中,参数必须为100)。 数字160是由于必须在图层的输入concatenate_1处接收到具有张量(无,无,160)的张量。
前两个参数是变量,具体取决于输入字符串的大小。
3)Lambda层,用于沿给定方向(在我们的示例中为左右)修剪张量(矩阵):
该层的输入是LSTM层,在该层之前是conv1_d层
3.1)Python层:
slicing_convolution = keras.layers.Lambda(lambda x: x[:,10:-10])(lstm_conv)
此操作与第2.1节中的操作完全相同。
3.2)Java层:
KerasLayer.registerLambdaLayer("lambda_3", new SameDiffLambdaLayer() { @Override public SDVariable defineLayer(SameDiff sameDiff, SDVariable sdVariable) { return sameDiff.stridedSlice(sdVariable, new int[]{ 0, 0, 10 }, new int[]{ (int)sdVariable.getShape()[0], (int)sdVariable.getShape()[1], (int)sdVariable.getShape()[2]-10}, new int[]{ 1, 1, 1 }); } @Override public InputType getOutputType(int layerIndex, InputType inputType) { return InputType.recurrent(100); } });
除了recurrent(100)参数外,该lambda层重复了先前的lambda层。 在上一层的说明中注明了为什么采用“ 100”。
在第2点和第3点,λ层位于LSTM层之后,因此使用循环类型。 但是,如果在lambda层之前没有LSTM,而是conv1d_1,则仍然有必要设置递归值(它看起来不一致,但是可以这样工作)。
4)Lambda层压缩前一层:
该层的输入是完全连接的层。
4.1)Python层:
squeeze = keras.layers.Lambda(lambda x: tf.squeeze( x, axis=-1))(dense)
4.2)Java层:
KerasLayer.registerLambdaLayer("lambda_4", new SameDiffLambdaLayer() { @Override public SDVariable defineLayer(SameDiff sameDiff, SDVariable sdVariable) { return sameDiff.squeeze(sdVariable, -1); } @Override public InputType getOutputType(int layerIndex, InputType inputType) { return InputType.feedForward(15); } });
该层的输入接收一个完全连接的层,该层的InputType feedForward(15),参数15不影响模型(允许使用任何整数值)。
下载导入的模型
该模型是通过ComputationGraph模块加载的:
ComputationGraph model = org.deeplearning4j.nn.modelimport.keras.KerasModelImport.importKerasModelAndWeights("/home/user/Models/model1_functional.h5");
将数据输出到Java控制台
在Java中,尤其是在DL4J中,张量是作为高性能Nd4j库的数组编写的,可以将其视为Python中Numpy库的类似物。
假设我们的输入字符串包含4个字符。 例如,根据一些编号,将符号表示为整数(作为索引)。 为它们创建一个相应维数(4)的数组。
例如,我们有4个索引编码的字符:1、3、4、8。
Java代码:
INDArray myArray = Nd4j.zeros(1,4);
控制台将显示每个输入元素的概率。
进口型号
原始神经网络的架构和权重均无错误地导入。 推理模式下的Keras和Java神经网络模型都对结果达成共识。
Python模型:
Java模型:
实际上,导入模型并不是那么简单。 下面我们将简要强调一些在某些情况下可能至关重要的观点。
1)补丁归一化层在递归层之后不起作用。 问题已经在GitHub上公开了将近一年[
16 ]。 例如,如果将此层添加到模型中(在接触层之后),则会出现以下错误:
Exception in thread "main" java.lang.IllegalStateException: Invalid input type: Batch norm layer expected input of type CNN, CNN Flat or FF, got InputTypeRecurrent(160) for layer index -1, layer name = batch_normalization_1
在实践中,该模型拒绝运行,因为在conv1d之后添加归一化层时引用了类似的错误。 在完全连接的层之后,附加功能可以完美地工作。
2)在完全连接层之后,设置“展平”层会导致错误。 在Stackoverflow [
17 ]上提到了类似的错误。 六个月以来,没有任何反馈。
当然,这不是使用DL4J时可能遇到的所有限制。
该模型的最终运行时间在此处[
18 ]。
结论
总之,可以注意到,将训练有素的Keras模型无痛地导入DL4J仅适用于简单的情况(当然,如果您没有这样的经验,并且确实有Java的良好命令)。
用户层越少,导入的模型就越轻松,但是如果网络体系结构复杂,则必须花费大量时间将其传输到DL4J。
已开发的导入模块的文档支持以及相关示例的数量似乎很潮湿。 在每个阶段,都会出现新的问题-如何注册Lambda层,参数的含义等。
考虑到神经网络架构的复杂性速度以及各层之间的交互速度,层的复杂性,DL4J尚未积极开发,以达到与人工神经网络一起使用的高端框架的水平。
无论如何,这些家伙都值得尊重他们的工作,并希望看到这个方向继续发展。
参考文献- 人工智能领域的5种最佳编程语言
- 深度学习框架力量得分2018
- 深度学习软件的比较
- 人工智能世界的9大框架
- DeepLearning4j。 可用型号
- DeepLearning4j。 Keras模型导入。 支持的功能。
- Deeplearning4j。 快速入门
- 讲座0:DeepLearning4j入门
- Deeplearing4j:Keras模型导入
- 讲座7 | Keras模型导入
- 安装TensorFlow for Java
- 使用Keras图层
- DeepLearning4j:KerasLayer类
- DeepLearning4j:SameDiffLambdaLayer.java
- DeepLearning4j:KerasLambdaTest.java
- DeepLearning4j:具有RecurrentInputType的BatchNorm
- StackOverFlow:使用deeplearning4j(https://deeplearning4j.org/)在Java中打开keras模型时出现问题
- GitHub:有关模型的完整代码
- Skymind:AI框架比较