Se você não possui Python, mas existe um modelo Keras e Java

Olá pessoal! Atualmente, na construção de modelos ML, o Python ocupa uma posição de liderança e é amplamente popular entre a comunidade de especialistas em Ciência de Dados [ 1 ].

Como a maioria dos desenvolvedores, o Python nos atrai com sua simplicidade e sintaxe concisa. Nós o usamos para resolver problemas de aprendizado de máquina usando redes neurais artificiais. No entanto, na prática, a linguagem de desenvolvimento de produto nem sempre é Python, e isso exige que resolvamos problemas de integração adicionais.

Neste artigo, falarei sobre as soluções que encontramos quando precisamos associar o modelo Keras do Python ao Java.

Em que prestamos atenção:

  • Pacote de recursos do modelo Keras e Java;
  • Preparando-se para trabalhar com a estrutura do DeepLearning4j (abreviação do DL4J);
  • Importando um modelo Keras para DL4J (com cuidado, a seção contém vários insights) - como registrar camadas, quais limitações o módulo de importação possui, como verificar os resultados de seu trabalho.

Por que ler?

  • Para economizar tempo no início, se você enfrentar a tarefa de integração semelhante;
  • Para descobrir se nossa solução é adequada para você e se você pode reutilizar nossa experiência.

imagem alt

Característica integral sobre a importância dos quadros de aprendizagem profunda [ 2 ].

Um resumo das estruturas de aprendizagem profunda mais populares pode ser encontrado aqui [ 3 ] e aqui [ 4 ].

Como você pode ver, a maioria dessas estruturas é baseada em Python e C ++: eles usam C ++ como o kernel para acelerar operações básicas e altamente carregadas, e Python como a interface de interação para acelerar o desenvolvimento.

De fato, muitas linguagens de desenvolvimento são muito mais extensas. Java é líder em desenvolvimento de produtos para grandes empresas e organizações. Algumas estruturas populares para redes neurais têm portas para Java na forma de ligadores JNI / JNA, mas nesse caso, é necessário criar um projeto para cada arquitetura e a vantagem do Java na questão do desfoque entre plataformas. Essa nuance pode ser extremamente importante em soluções replicadas.

Outra abordagem alternativa é usar o Jython para compilar no bytecode Java; mas há uma desvantagem aqui: suporte apenas à segunda versão do Python, além da capacidade limitada de usar bibliotecas Python de terceiros.

Para simplificar o desenvolvimento de soluções de redes neurais em Java, a estrutura DeepLearning4j (DL4J para abreviar) está sendo desenvolvida. O DL4, além da API Java, oferece um conjunto de modelos pré-treinados [ 5 ]. Em geral, essa ferramenta de desenvolvimento é difícil de competir com o TensorFlow. O TensorFlow supera o DL4J com documentação mais detalhada e vários exemplos, recursos técnicos, tamanhos de comunidade e desenvolvimento acelerado. No entanto, a tendência que Skymind adere é bastante promissora. Concorrentes significativos em Java para esta ferramenta ainda não estão visíveis.

A biblioteca DL4J é uma das poucas (se não a única) que possibilita a importação de modelos Keras; ela expande sua funcionalidade com camadas familiares a Keras [ 6 ]. A biblioteca DL4J contém um diretório com exemplos da implementação de modelos ML de rede neural (exemplo dl4j). No nosso caso, as sutilezas da implementação desses modelos em Java não são tão interessantes. Atenção mais detalhada será dada à importação do modelo treinado Keras / TF para Java usando métodos DL4J.

Introdução


Antes de começar, você precisa instalar os programas necessários:

  1. Java versão 1.7 (versão de 64 bits) e superior.
  2. Sistema de criação de projetos do Apache Maven.
  3. IDE para escolher: Intellij IDEA, Eclipse, Netbeans. Os desenvolvedores recomendam a primeira opção e, além disso, os exemplos de treinamento disponíveis são discutidos.
  4. Git (para clonar um projeto no seu PC).

Uma descrição detalhada com um exemplo de lançamento pode ser encontrada aqui [ 7 ] ou no vídeo [ 8 ].

Para importar o modelo, os desenvolvedores do DL4J sugerem o uso do módulo de importação KerasModelImport (publicado em outubro de 2016). O funcional do módulo suporta ambas as arquiteturas de modelos da Keras - é Sequencial (analógico na classe Java MultiLayerNetwork) e Funcional (analógico na classe Java ComputationGraph). O modelo é importado como um todo no formato HDF5 ou 2 arquivos separados - o peso do modelo com a extensão h5 e o arquivo json que contém a arquitetura de rede neural.

Para um início rápido, os desenvolvedores do DL4J prepararam uma análise passo a passo de um exemplo simples no conjunto de dados da íris Fisher para um modelo do tipo Sequential [ 9 ]. Outro exemplo de treinamento foi considerado da perspectiva de importação de modelos de duas maneiras (1: no formato HDF5 completo; 2: em arquivos separados - pesos do modelo (extensão h5) e arquitetura (extensão json)), seguidos de uma comparação dos resultados dos modelos Python e Java [ 10 ]. Isso conclui a discussão dos recursos práticos do módulo de importação.

Também existe TF em Java, mas está em um estado experimental e os desenvolvedores não dão nenhuma garantia de sua operação estável [ 11 ]. Há problemas com o controle de versão e o TF em Java possui uma API incompleta - e é por isso que essa opção não será considerada aqui.

Características do modelo Keras / TF original:


A importação de uma rede neural é simples. Mais detalhadamente no código, analisaremos um exemplo de integração de uma rede neural com arquitetura mais complicada.

Você não deve entrar nos aspectos práticos deste modelo, pois é indicativo do ponto de vista da contabilização de camadas (em particular, registro de camadas Lambda), algumas sutilezas e limitações do módulo de importação, bem como o DL4J como um todo. Na prática, as nuances observadas podem exigir ajustes na arquitetura da rede ou abandonar completamente a abordagem de iniciar o modelo através do DL4J.

Características do modelo:

1. Tipo de modelo - Funcional (rede com ramificação);

2. Os parâmetros de treinamento (o tamanho do lote, o número de eras) são selecionados pequenos: o tamanho do lote - 100, o número de eras - 10, etapas por época - 10;

3. 13 camadas, um resumo das camadas é mostrado na figura:

imagem alt

Descrição curta da camada
  1. input_1 - camada de entrada, aceita um tensor bidimensional (representado por uma matriz);
  2. lambda_1 - a camada do usuário, no nosso caso, torna o preenchimento no TF do tensor os mesmos valores numéricos;
  3. embedding_1 - cria Embedding (representação vetorial) para a sequência de entrada de dados de texto (converte o tensor 2D em 3D);
  4. conv1d_1 - camada convolucional 1-D;
  5. lstm_2 - camada LSTM (após a incorporação da camada_1 (n ° 3));
  6. lstm_1 - camada LSTM (segue a camada conv1d (n ° 4));
  7. lambda_2 é a camada do usuário em que o tensor é truncado após a camada lstm_2 (nº 5) (a operação oposta ao preenchimento na camada lambda_1 (nº 2));
  8. lambda_3 é a camada do usuário em que o tensor é truncado após as camadas lstm_1 (nº 6) e conv1d_1 (nº 4) (a operação oposta ao preenchimento na camada lambda_1 (nº 2));
  9. concatenate_1 - ligação de camadas truncadas (nº 7) e (nº 8);
  10. dense_1 - uma camada totalmente conectada de 8 neurônios e uma função de ativação linear exponencial "elu";
  11. batch_normalization_1 - camada de normalização;
  12. dense_2 - camada totalmente conectada de 1 neurônio e função de ativação sigmóide "sigmóide";
  13. lambda_4 - uma camada de usuário em que a compressão da camada anterior (compressão no TF) é realizada.

4. Função de perda - binary_crossentropy

loss= frac1N sum1N(ytrue logdocdot(ypred)+(1ytrue) logdocdot(1ypred))



5. Métrica de qualidade do modelo - média harmônica (medida F)

F=2 fracPrecisão repetiçãodechamadasPrecisão+recuperação


No nosso caso, a questão das métricas de qualidade não é tão importante quanto a correção da importação. A correção da importação é determinada pela coincidência dos resultados nos modelos NN Python e Java trabalhando no modo Inferência.

Importe modelos Keras no DL4J:


Versões usadas: Tensorflow 1.5.0 e Keras 2.2.5. No nosso caso, o modelo do Python foi carregado como um todo pelo arquivo HDF5.

# saving model model1.save('model1_functional.h5') 

Ao importar um modelo para o DL4J, o módulo de importação não fornece métodos de API para passar parâmetros adicionais: o nome do módulo tensorflow (de onde as funções foram importadas ao criar o modelo).

De um modo geral, o DL4J funciona apenas com as funções do Keras, uma lista exaustiva é fornecida na seção Keras Import [ 6 ]; portanto, se o modelo foi criado no Keras usando métodos do TF (como no nosso caso), o módulo de importação não poderá identificá-los.

Diretrizes gerais para importar um modelo


Obviamente, trabalhar com o modelo Keras implica em seu treinamento repetido. Para esse fim, para economizar tempo, os parâmetros de treinamento foram definidos (1 época) e 1 etapa por época (steps_per_epoch).

Quando você importa um modelo pela primeira vez, especialmente com camadas personalizadas exclusivas e combinações raras de camadas, o sucesso é improvável. Portanto, é recomendável executar o processo de importação iterativamente: reduza o número de camadas do modelo Keras até que você possa importar e executar o modelo em Java sem erros. Em seguida, adicione uma camada de cada vez ao modelo Keras e importe o modelo resultante para Java, resolvendo os erros que ocorrem.

Usando a função de perda de TF


Para provar que, ao importar para Java, a função de perda do modelo treinado deve ser do Keras, usamos log_loss do tensorflow (como o mais semelhante à função custom_loss). Temos o seguinte erro no console:

 Exception in thread "main" org.deeplearning4j.nn.modelimport.keras.exceptions.UnsupportedKerasConfigurationException: Unknown Keras loss function log_loss. 

Substituindo métodos TF por Keras


No nosso caso, as funções do módulo TF são usadas 2 vezes e em todos os casos são encontradas apenas nas camadas lambda.

Camadas Lambda são camadas personalizadas usadas para adicionar uma função arbitrária.

Nosso modelo possui apenas 4 camadas lambda. O fato é que em Java é necessário registrar essas camadas lambda manualmente através do KerasLayer.registerLambdaLayer (caso contrário, obtemos um erro [ 12 ]). Nesse caso, a função definida dentro da camada lambda deve ser uma função das bibliotecas Java correspondentes. Em Java, não há exemplos de registro dessas camadas, além de documentação abrangente para isso; um exemplo está aqui [ 13 ]. Considerações gerais foram emprestadas de exemplos [ 14 , 15 ].

Considere sequencialmente registrar todas as camadas lambda do modelo em Java:

1) Camada Lambda para adicionar constantes ao tensor (matriz) um número finito de vezes ao longo de determinadas direções (no nosso caso, esquerda e direita):

A entrada desta camada está conectada à entrada do modelo.

1.1) Camada Python:

 padding = keras.layers.Lambda(lambda x: tf.pad(x, paddings=[[0, 0], [10, 10]], constant_values=1))(embedding) 

Para maior clareza, as funções dessa camada funcionam, substituímos explicitamente valores numéricos nas camadas python.

Tabela com um exemplo de um tensor arbitrário 2x2
Era 2x2Tornou-se 2x22
[[ 1 , 2 ],
[ 3 , 4 ]
[[37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 1 , 2 , 37, 37, 37, 37, 37, 37, 37, 37, 37, 37],
[37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 3 , 4 , 37, 37, 37, 37, 37, 37, 37, 37, 37, 37]]


1.2) Camada 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); } }); 

Em todas as camadas lambda registradas em Java, 2 funções são redefinidas:
A primeira função "definelayer" é responsável pelo método usado (não é um fato óbvio: esse método só pode ser usado sob o nn () backend); getOutputType é responsável pela saída da camada registrada, o argumento é um parâmetro numérico (aqui 20, mas em geral qualquer valor inteiro é permitido). Parece inconsistente, mas funciona assim.

2) Camada Lambda para aparar o tensor (matriz) ao longo de determinadas direções (no nosso caso, esquerda e direita):

Nesse caso, a camada LSTM entra na entrada da camada lambda.

2.1) Camada Python:

 slicing_lstm = keras.layers.Lambda(lambda x: x[:, 10:-10])(lstm) 

Tabela com um exemplo de um tensor arbitrário 2x22x5
Era 2x22x5Tornou-se 2x2x5
[[[1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1 , 2,3,4,5], [1,2,3,4,5], [ 1 , 2 , 3 , 4 , 5 ], [ 1 , 2 , 3 , 4 , 5 ], [1,2 , 3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3 , 4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4 , 5], [1,2,3,4,5]],

[[1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [ 1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1, 2,3,4,5], [1,2,3,4,5], [ 1 , 2 , 3 , 4 , 5 ], [ 1 , 2 , 3 , 4 , 5 ], [1,2, 3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3, 4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4,5], [1,2,3,4, 5], [1,2,3,4,5]]]
[[[ 1 , 2 , 3 , 4 , 5 ], [ 1 , 2 , 3 , 4 , 5 ]],
[[ 1 , 2 , 3 , 4 , 5 ], [ 1 , 2 , 3 , 4 , 5 ]]]


2.2) Camada 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); } }); 

No caso dessa camada, o parâmetro InputType mudou de feedforward (20) para recorrente (60). No argumento recorrente, o número pode ser qualquer número inteiro (diferente de zero), mas sua soma com o argumento recorrente da próxima camada lambda deve fornecer 160 (ou seja, na próxima camada, o argumento deve ser 100). O número 160 é devido ao fato de que o tensor com a dimensão (Nenhum, Nenhum, 160) deve ser recebido na entrada concatenate_1 da camada.

Os 2 primeiros argumentos são variáveis, dependendo do tamanho da string de entrada.

3) Camada Lambda para aparar o tensor (matriz) ao longo de determinadas direções (no nosso caso, esquerda e direita):

A entrada dessa camada é a camada LSTM, na frente da qual está a camada conv1_d

3.1) Camada Python:

 slicing_convolution = keras.layers.Lambda(lambda x: x[:,10:-10])(lstm_conv) 

Esta operação é completamente idêntica à operação no parágrafo 2.1.

3.2) Camada 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); } }); 

Essa camada lambda repete a camada lambda anterior, com exceção do parâmetro recorrente (100). Por que "100" é tirado é mencionado na descrição da camada anterior.

Nos pontos 2 e 3, as camadas lambda estão localizadas após as camadas LSTM, portanto, o tipo recorrente é usado. Mas se antes da camada lambda não havia LSTM, mas conv1d_1, ainda é necessário definir recorrente (parece inconsistente, mas funciona assim).

4) Camada Lambda para compactar a camada anterior:

A entrada dessa camada é uma camada totalmente conectada.

4.1) Camada Python:

  squeeze = keras.layers.Lambda(lambda x: tf.squeeze( x, axis=-1))(dense) 

Tabela com um exemplo de um tensor arbitrário 2x4x1
Era 2x4x1Tornou-se 2x4
[[[ 1], [2], [3], [4]] ,

[ [1], [2], [3], [4] ]]
[[ 1, 2, 3, 4 ],
[ 1, 2, 3, 4 ]]


4.2) Camada 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); } }); 

A entrada desta camada recebe uma camada totalmente conectada, InputType para esta camada feedForward (15), o parâmetro 15 não afeta o modelo (qualquer valor inteiro é permitido).

Baixar modelo importado


O modelo é carregado através do módulo ComputationGraph:

 ComputationGraph model = org.deeplearning4j.nn.modelimport.keras.KerasModelImport.importKerasModelAndWeights("/home/user/Models/model1_functional.h5"); 

Saída de dados para o console Java


Em Java, em particular no DL4J, os tensores são gravados como matrizes da biblioteca Nd4j de alto desempenho, que pode ser considerada um análogo da biblioteca Numpy no Python.

Digamos que nossa string de entrada consista em 4 caracteres. Os símbolos são representados como números inteiros (como índices), por exemplo, de acordo com alguma numeração. Uma matriz da dimensão correspondente (4) é criada para eles.

Por exemplo, temos 4 caracteres codificados em índice: 1, 3, 4, 8.

Código em Java:

 INDArray myArray = Nd4j.zeros(1,4); // one row 4 column array myArray.putScalar(0,0,1); myArray.putScalar(0,1,3); myArray.putScalar(0,2,4); myArray.putScalar(0,3,8); INDArray output = model.outputSingle(myArray); System.out.println(output); 

O console exibirá as probabilidades para cada elemento de entrada.

Modelos importados


A arquitetura da rede neural original e os pesos são importados sem erros. Os modelos de rede neural Keras e Java no modo Inferência concordam com os resultados.

Modelo Python:

imagem alt

Modelo Java:

imagem alt

Na realidade, importar modelos não é tão simples. Abaixo, destacaremos brevemente alguns pontos que, em alguns casos, podem ser críticos.

1) A camada de normalização do patch não funciona após as camadas recursivas. A questão está aberta no GitHub há quase um ano [ 16 ]. Por exemplo, se você adicionar essa camada ao modelo (após a camada de contato), obteremos o seguinte erro:

 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 

Na prática, o modelo se recusou a funcionar, citando um erro semelhante quando a camada de normalização foi adicionada após a conv1d. Após uma camada totalmente conectada, a adição funciona na perfeição.

2) Após uma camada totalmente conectada, a configuração da camada Achatar resulta em um erro. Um erro semelhante é mencionado no Stackoverflow [ 17 ]. Por seis meses, sem feedback.

Definitivamente, essas não são todas as restrições que você pode encontrar ao trabalhar com o DL4J.
O tempo final de operação para o modelo é aqui [ 18 ].

Conclusão


Concluindo, pode-se notar que os modelos Keras treinados importados sem dor para o DL4J são possíveis apenas em casos simples (é claro, se você não tiver essa experiência e, de fato, um bom conhecimento de Java).

Quanto menos camadas de usuário, mais indolor o modelo será importado, mas se a arquitetura de rede for complexa, você precisará gastar muito tempo transferindo-o para o DL4J.

O suporte documental do módulo de importação desenvolvido, o número de exemplos relacionados, parecia bastante úmido. Em cada estágio, novas questões surgem - como registrar camadas Lambda, significado dos parâmetros, etc.

Dada a velocidade da complexidade das arquiteturas de redes neurais e a interação entre as camadas, a complexidade das camadas, o DL4J ainda precisa se desenvolver ativamente para atingir o nível de estruturas de ponta para trabalhar com redes neurais artificiais.

De qualquer forma, os rapazes são dignos de respeito pelo seu trabalho e gostariam de ver a continuação do desenvolvimento dessa direção.

Referências

  1. Entre os 5 melhores idiomas de programação para inteligência artificial
  2. Pontuação do Power Framework do Deep Learning 2018
  3. Comparação de software de aprendizado profundo
  4. As 9 principais estruturas do mundo da inteligência artificial
  5. DeepLearning4j. Modelos disponíveis
  6. DeepLearning4j. Importação de modelo Keras. Recursos suportados.
  7. Deeplearning4j. Início rápido
  8. Aula 0: Introdução ao DeepLearning4j
  9. Deeplearing4j: importação do modelo Keras
  10. Palestra 7 | Importação do modelo Keras
  11. Instale o TensorFlow para Java
  12. Usando Camadas Keras
  13. DeepLearning4j: Class KerasLayer
  14. DeepLearning4j: SameDiffLambdaLayer.java
  15. DeepLearning4j: KerasLambdaTest.java
  16. DeepLearning4j: BatchNorm com RecurrentInputType
  17. StackOverFlow: Problema ao abrir um modelo keras em java com deeplearning4j (https://deeplearning4j.org/)
  18. GitHub: código completo para o modelo em questão
  19. Skymind: Comparação de estruturas de IA

Source: https://habr.com/ru/post/pt475338/


All Articles