Tensorflow: Interfaz Java

Creado en 9 nov. 2015  ·  112Comentarios  ·  Fuente: tensorflow/tensorflow

Problema para rastrear el esfuerzo de la interfaz swig para java. Implementación iniciada: se actualizará con el progreso. Si alguien tiene algún comentario / consejo, ¡no dude en unirse a la discusión!

Comentario más útil

Actualización: pude hacer que las cosas funcionen con éxito con javacpp (gracias a @saudet ) y hacer que los programas Java lean / ejecuten modelos de TensorFlow.

https://medium.com/google-cloud/how-to-invoke-a-trained-tensorflow-model-from-java-programs-27ed5f4f502d#.tx8nyds5v

Todos 112 comentarios

¡Bonito!

Moviendo este comentario aquí desde https://github.com/tensorflow/tensorflow/issues/3 :


Hay una suite de pruebas con convergencia bastante buena, pero actualmente es principalmente Python con algunas pruebas de C ++. También hay muchas funciones para crear gráficos que actualmente son solo Python, en particular la función de diferenciación automática, aunque eso no importa para la evaluación de gráficos en Java. Hay planes para trasladar esta funcionalidad al C ++ subyacente en el futuro, momento en el que los enlaces Java SWIG serían más útiles para crear gráficos.

Si alguien acepta el desafío SWIG de Java, estaremos encantados de aceptarlo, pendiente de revisión, etc., momento en el que sería parte de nuestras pruebas continuas. Los detalles de la aceptación de contribuciones están cambiando en este momento, pero eso se estabilizará.

Hola chicos
También estamos interesados ​​en adaptar TensorFlow para Java. @ravwojdyla ¿Ha empezado a trabajar en la Swig Interface para Java? Si es así, podríamos unirnos a nuestros esfuerzos y colaborar en eso.

Hola,
Estoy trabajando en un ajuste SWIG de la API principal de C ++. Puedes ver mi progreso hasta ahora en mi bifurcación, pero lo que hay ahí arriba no está terminado; Actualmente estoy experimentando un problema en el que #include "tensorflow/core/lib/core/error_codes.pb.h" no se puede resolver y no puedo encontrar el archivo deseado en ningún lugar dentro de los archivos del proyecto. Cualquier tipo de aportación será muy apreciada.

Hay ajustes preestablecidos de javacpp disponibles para bibliotecas como Caffe y OpenCV. Consulte también https://github.com/bytedeco/javacpp-presets/issues/111. Java-cpp habilita también IOS con RoboVM

/ cc @saudet

@pslam - Pude trabajar un poco en esto - ¡definitivamente me vendría bien un poco de ayuda!

Hola chicos, creo que tengo enlaces bastante funcionales para JavaCPP: https://github.com/bytedeco/javacpp-presets/tree/master/tensorflow. Avíseme si ve algo que se pueda hacer con SWIG, pero no con JavaCPP. Definitivamente podría usar los comentarios. (¡Gracias por el cc @bhack!)

Muy bien hecho @saudet! Casi he terminado un ajuste SWIG, pero parece que su implementación funciona igual de bien. No veo nada que mi envoltura SWIG pueda hacer que la suya no pueda hacer. JavaCPP parece muy bueno, tendré que considerar su uso para proyectos futuros.

Hola @kylevedder , ¿has resuelto el problema relacionado con error_codes.pb.h ?
[Editado]
Todos los archivos .pb.h se compilan a partir de .proto

@tngan Sí, eso es lo que también descubrí. Además, los archivos .proto de este proyecto requieren el uso de ProtoBuff3. Estoy usando Ubuntu 14.04 y ProtoBuff3 no estaba disponible en mi administrador de paquetes, así que lo compilé desde la fuente, que obtuve de la versión beta 3.0.0 .

El obstáculo actual que estoy tratando de resolver es cómo hacer que ProtoBuff recurra a todo el árbol de archivos y compile los archivos .proto en archivos .h y .cc ; Hacer cada carpeta por partes da como resultado fallas debido a dependencias insatisfechas sobre otros archivos que aún no se han compilado .proto .

@kylevedder ¿Están sus envoltorios SWIG en un repositorio separado o está trabajando en el repositorio de tensorflow? protoc funciona de manera similar a otros compiladores. Si está trabajando en el repositorio de tensorflow o está usando Bazel, entonces necesitaría configurar los objetivos de compilación de protobuf y las dependencias entre ellos.

Si está trabajando en un repositorio separado y usa un sistema de compilación diferente, entonces necesitaría usar el complemento protobuf para ese sistema de compilación.

Estaré encantado de ayudarlo a configurar la compilación si lo desea.

@davidzchen Gracias por la oferta, cualquier ayuda es muy apreciada.

Lo que tengo hasta ahora:

Ya configuré Bazel y obtuve la compilación en un archivo .whl , que luego entregué a pip y confirmé que puedo ejecutar el programa First TensorFlow .

He generado archivos de contenedor SWIG en mi repositorio bifurcado. Están en una carpeta debajo de core/javaWrapper . [[enlace] (https://github.com/kylevedder/tensorflow/tree/master/tensorflow/core/javaWrapper)]

Lo que estoy tratando de hacer:

En última instancia, mi objetivo es generar un archivo .so que se pueda llamar como una biblioteca nativa en Java. Actualmente, estoy intentando usar g ++ para compilar todo el sistema en un archivo .so ; sin embargo, los archivos .proto primero deben expandirse a .h sy .cc s antes de esta compilación, y eso es lo que estoy tratando de hacer con protoc .

Puede ver mi intento de un script de ajuste aquí para tener una mejor idea de a qué me refiero, aunque hasta ahora todos mis intentos de usar protoc han sido directorio por directorio y, en consecuencia, no en el guión.

Finalmente, cualquier comentario sobre áreas de mejora será muy apreciado. ¡Gracias!

@kylevedder Ya tengo una .so como parte de los ajustes preestablecidos de JavaCPP: https://github.com/bytedeco/javacpp-presets/tree/master/tensorflow. Gracias a Bazel, es realmente sencillo. Solo aplique un parche como este:

diff -ruN tensorflow/tensorflow/cc/BUILD tensorflow-patch/tensorflow/cc/BUILD
--- tensorflow/tensorflow/cc/BUILD  2015-11-22 00:00:02.441829192 +0900
+++ tensorflow-patch/tensorflow/cc/BUILD    2015-11-14 11:15:12.689330351 +0900
@@ -75,6 +75,17 @@
     ],
 )

+cc_binary(
+    name = "libtensorflow.so",
+    copts = tf_copts(),
+    linkshared = 1,
+    deps = [
+        ":cc_ops",
+        "//tensorflow/core:kernels",
+        "//tensorflow/core:tensorflow",
+    ],
+)
+
 filegroup(
     name = "all_files",
     srcs = glob(

Y ejecuta Bazel así, por ejemplo:

bazel build -c opt //tensorflow/cc:libtensorflow.so

AFAIK, esto debería engullir casi cualquier cosa de interés para la API de C ++.

@saudet ¿Hay alguna razón por la que está utilizando una regla cc_binary para construir la biblioteca compartida en lugar de cc_library ? Puede tener una regla cc_library con el nombre tensorflow y el objetivo de compilación creará una biblioteca compartida llamada libtensorflow.so .

@kylevedder Si su objetivo es generar un archivo .so , entonces funcionaría algo similar a lo que sugirió @saudet .

Si necesita usar los protos de TensorFlow en código Java, entonces necesitaría agregar dependencias desde sus objetivos de compilación java_* Bazel a los objetivos proto_library que generan las clases Java a partir de .proto archivos.

Todavía tenemos un poco de trabajo por hacer antes de que abramos las reglas nativas proto_library (ver bazelbuild / bazel # 52), pero mientras tanto, TensorFlow usa cc_proto_library y py_proto_library reglas proporcionadas por protobuf , y para Java, debería poder usar la regla Java genproto que se incluye con Bazel . Verificaré con el equipo para averiguar cuál es la línea de tiempo para proto_library y si valdría la pena unificar las reglas proporcionadas por Protobuf con genproto .

Algunos otros comentarios:

  • Creo que sería mejor mantener los nombres de directorio consistentes y usar java_wrapper lugar de javaWrapper
  • ¿Quizás un mejor lugar para el contenedor de Java sería //tensorflow/java/wrapper lugar de //tensorflow/core/java_wrapper ?
  • Internamente, tenemos algunas reglas de compilación que toman .swig archivos y generan las fuentes. Esto es más ideal porque evitaríamos registrar los archivos generados. Puedo echar un vistazo para ver lo difícil que sería para nosotros agregar algunas reglas de compilación SWIG para Bazel para facilitar este tipo de cosas.

@davidzchen Sin motivo en particular. Soy nuevo en Bazel y solo uso linkshared=1 como he visto mencionado en la lista de correo funcionó. ¡Gracias por el consejo! Estaré actualizando eso.

@saudet ¡Gracias! Solo estaba comprobando para asegurarme de que no fuera un problema con Bazel. :) No dudes en avisarme o abrir un error si tienes algún problema.

@saudet Gracias por la información sobre el uso de Bazel. Yo también soy nuevo en él y no me di cuenta de que era capaz de generar un .so de esa manera.

@davidzchen Gracias por el apéndice sobre el uso de cc_library , modifiqué el ejemplo de @saudet en consecuencia cuando implementé mi compilación de contenedor de Bazil . Además, gracias por la información relacionada con la estructura del directorio; He actualizado la estructura de mi carpeta para alinearla con sus sugerencias.

Además, no estaba muy claro en mi comentario anterior sobre la generación de archivos .so ; Si bien mi objetivo es generar un archivo .so partir de la fuente original, también quiero incluir el archivo .cxx que SWIG genera dentro del .so para facilitar el JNI llamadas. Actualmente, me encuentro con un problema en el que no puedo compilar el archivo .cxx generado por SWIG; está tratando de hacer referencia a JNI.h , un encabezado ubicado en $JAVA_HOME/include/ , pero parece que no puedo hacer que Bazel entienda la ruta de inclusión externa.

@davidzchen Hum, no, cc_library no funciona. No veo ninguna otra forma de hacer que Bazel pase la opción -shared al compilador: http://bazel.io/docs/be/c-cpp.html.

@saudet No creo que tengas que pasar -shared tú mismo. cc_library debería estar construyendo un .so por defecto. Eso funciona para ti?

@kylevedder No podrá agregar los encabezados JNI de esa manera ya que están fuera del espacio de trabajo. Sin embargo, Bazel incluye el JDK local como un repositorio local y proporciona una serie de objetivos integrados (consulte jdk.WORKSPACE y el jdk.BUILD ) que puede utilizar para depender del JDK local. Estos se incluyen en cada espacio de trabajo de Bazel de forma predeterminada.

El propio Bazel usa JNI e interactúa con el JDK local de esta manera (ver src/main/native/BUILD ). En este archivo BUILD, hay dos genrule s para copiar los encabezados JNI y un destino cc_library para la biblioteca que está construyendo que usa JNI que depende de los encabezados, y un includes = ["."] para que el código C ++ pueda incluir el encabezado JNI con #include <jni.h> . Actualmente, esto no está documentado porque estamos trabajando en una serie de mejoras en el mecanismo del repositorio externo, y el nombre @local-jdk podría cambiar, pero podemos usarlo para TensorFlow y cualquier otro proyecto de Bazel que use JNI mientras tanto. .

Aquí hay un parche para su archivo BUILD que agrega los objetivos genrule para copiar los encabezados JNI que necesita y algunos cambios en el objetivo cc_library para configurar las dependencias correctas, a saber:

  1. Agregue jni.h y jni_md.h , que los genrule s copian al paquete actual a srcs
  2. Agregue una dependencia en //tensorflow/core para que pueda incluir los encabezados debajo de tensorflow/core/public . Tenga en cuenta que los encabezados o cualquier archivo fuente en un directorio separado están en un paquete separado desde el punto de vista de Bazel, y deberá agregar una dependencia en el destino de compilación que contiene esos archivos.
diff --git a/tensorflow/core/java/wrapper/BUILD b/tensorflow/core/java/wrapper/BUILD
index 72b4076..04a3394 100644
--- a/tensorflow/core/java/wrapper/BUILD
+++ b/tensorflow/core/java/wrapper/BUILD
@@ -7,10 +7,30 @@ exports_files(["LICENSE"])
 load("/tensorflow/tensorflow", "tf_copts")
 load("/tensorflow/tensorflow", "tf_gen_op_wrappers_cc")

+genrule(
+    name = "copy_link_jni_md_header",
+    srcs = ["//external:jni_md_header-linux"],
+    outs = ["jni_md.h"],
+    cmd = "cp -f $< $@",
+)
+
+genrule(
+    name = "copy_link_jni_header",
+    srcs = ["//external:jni_header"],
+    outs = ["jni.h"],
+    cmd = "cp -f $< $@",
+)
+
 cc_library(
     name = "java_wrapper",
-    srcs = glob(["*.cc","*.cxx","*.h"]),
-    copts = ["-I$$JAVA_HOME/include/", "-I$$JAVA_HOME/include/linux/"],
+    srcs = glob(["*.cc", "*.cxx", "*.h"]) + [
+        ":jni.h",
+        ":jni_md.h",
+    ],
+    includes = ["."],
+    deps = [
+        "//tensorflow/core",
+    ],
     visibility = ["//visibility:public"],
 )

Tenga en cuenta que, en general, las acciones de compilación en Bazel se ejecutan desde la raíz del árbol de origen, y necesitaría cambiar las inclusiones en su archivo SWIG de la siguiente manera y luego volver a generar los archivos C ++ para que tengan las inclusiones correctas como bien:

diff --git a/tensorflow/core/java/wrapper/tensor_c_api.i b/tensorflow/core/java/wrapper/tensor_c_api.i
index d08b571..9ab1fa1 100644
--- a/tensorflow/core/java/wrapper/tensor_c_api.i
+++ b/tensorflow/core/java/wrapper/tensor_c_api.i
@@ -1,8 +1,8 @@
 %module tensor_c_api_module
 %{
-#include "../../public/tensor_c_api.h"
+#include "tensorflow/core/public/tensor_c_api.h"
 %}
-%include "../../public/tensor_c_api.h"
+%include "tensorflow/core/public/tensor_c_api.h"
 %include "stddef.h"

Una vez que esto funcione, tendrá la compilación JNI configurada para Linux, ya que copy_link_jni_md_header genrule solo copia el encabezado específico de Linux. Para que copie el encabezado JNI correcto específico de la plataforma, tendríamos que hacer lo siguiente:

  1. Configure cpu config_setting s para otras plataformas. Actualmente, tensorflow tiene un config_setting por --cpu=darwin en tensorflow/python/BUILD . Probablemente deberíamos mover ese paquete a un paquete más apropiado como //tensorflow/core . Básicamente, querríamos el mismo conjunto de config_setting s que Bazel (ver src/BUILD ).
  2. Haga que copy_link_jni_md_header copie el encabezado JNI derecho en función de la configuración que se establezca usando select() , similar al de Bazel . Nuestro genrule tendría el siguiente aspecto:
genrule(
    name = "copy_link_jni_md_header",
    srcs = select({
        "//tensorflow/core:darwin": ["//external:jni_md_header-darwin"],
        "//tensorflow/core:darwin_x86_64": ["//external:jni_md_header-darwin"],
        "//tensorflow/core:freebsd": ["//external:jni_md_header-freebsd"],
        "//conditions:default": ["//external:jni_md_header-linux"],
    }),
    outs = ["jni_md.h"],
    cmd = "cp -f $< $@",
)

Estaré encantado de ayudarte con esto si tienes algún problema. Hazme saber si esto funciona para ti.

@davidzchen cc_library genera un montón de archivos .a, pero ningún archivo .so. Estoy usando 0.1.0 como se recomendó anteriormente para TensorFlow ... ¿Quizás esté arreglado en 0.1.1? Tendré que intentarlo de nuevo.

@davidzchen Muchas gracias por tu ayuda. He seguido sus instrucciones y he actualizado tanto el archivo contenedor de Java archivo SWIG .i como sugirió. Además, moví el script de ajuste de core/java/wrapper al directorio raíz y actualicé los enlaces en consecuencia.

Por ahora, me he saltado la generalización de genrule para el archivo jni_md.h , en lugar de centrarme en intentar obtener libtensorflow.so construido. Desafortunadamente, me parece que libtensorflow.so no se está generando; Terminé buscando en todo mi sistema de archivos algo llamado alguna variante de "libtensorflow" y no apareció nada relevante. Puede tener un nombre diferente o puede tratarse de un simple caso de error del usuario. Además, hay una posibilidad de que pueda estar relacionado con el tema que @saudet está experimentando con el cc_library regla para .so generación.

Una vez más, gracias por toda su ayuda, se lo agradezco mucho.

Lo siento, resulta que estaba equivocado. Para construir un .so que incluya las dependencias transitivas, lo que hizo @saudet usando cc_binary con linkshared = 1 y name = "libtensorflow.so" fue correcto. De la documentación cc_binary.linkshared :

Crea una biblioteca compartida. Para habilitar este atributo, incluya linkshared = 1 en su regla. De forma predeterminada, esta opción está desactivada. Si lo habilita, debe nombrar su binario libfoo.so (o cualquiera que sea la convención de nomenclatura de las bibliotecas en la plataforma de destino) para obtener un valor sensato de foo.

La principal diferencia entre los .so construidos por cc_library objetivos y los .so construidos con cc_binary usando el método descrito anteriormente es que el cc_library artefactos solo contienen el código en srcs . Esta es la razón por la que la construcción de objetivos cc_library sin srcs y solo deps , como //tensorflow/core , no produce ningún artefacto. Por otro lado, cc_binary objetivos se vincularán en todas las dependencias transitivas.

Me disculpo por la confusión. Quizás deberíamos mejorar nuestra documentación y agregar un ejemplo sobre la construcción de .so s.

Supongo que deberías seguir esos pasos para construir Tensorflow y todas sus dependencias. Estamos trabajando en la migración de TensorFlow a node.js, y he implementado un script de shell para compilar y obtener solo las fuentes esenciales de todo el repositorio:
https://github.com/node-tensorflow/node-tensorflow/blob/1.0.0/tools/install.sh#L233 -L282

@davidzchen Gracias por la información sobre la creación de un .so . He actualizado mi configuración en consecuencia y he creado un tensorflow/core/java/wrapper/example con un tester _extremely_ basic para demostrar que la función JNI llama al trabajo .so . Tenga en cuenta que createWrapper.sh debe ejecutarse antes de ejecutar compileAndRun.sh .

Intentaré mejorar la envoltura SWIG y dar un mejor ejemplo, el que tengo ahora es simplemente una prueba mínima de las uniones funcionales.

Finalmente, quiero agradecer a @davidzchen y @saudet por toda su ayuda; No hubiera podido hacer esto sin ellos.

¡Bonito! ¡Gracias por trabajar en esto, @kylevedder!

Si está interesado, puedo intentar integrar sus scripts createWrapper.sh y compileAndRun.sh en la compilación de Bazel 1) creando la regla Skylark SWIG y 2) usando las reglas de Java de Bazel para compilar el código Java.

@davidzchen ¡ Eso sería genial! Trabajaré para mejorar la envoltura SWIG y el ejemplo base.

Finalicé los ajustes preestablecidos para JavaCPP y porté la muestra example_trainer.cc :
https://github.com/bytedeco/javacpp-presets/tree/master/tensorflow
¡Espero comparar esto con una envoltura equivalente usando SWIG!

Parece que el enlace de la API está roto: http://bytedeco.org/javacpp-presets/tensorflow/apidocs/

@verdiyanto Lo siento, todavía no tengo CI, pero cargar los documentos de la API es bastante fácil, así que al menos lo he hecho. ¡Disfrutar!

@saudet ¡ Buen trabajo con los ajustes preestablecidos de JavaCPP!

Una actualización de mi trabajo: he trabajado un poco más en el contenedor SWIG, y puede ver el trabajo que he realizado aquí . Sin embargo, estoy en una encrucijada y no estoy seguro de cuál es la mejor manera de proceder.

Soy bastante nuevo en SWIG, dado que este es mi primer proyecto importante que lo usa, así que leí la documentación de SWIG sobre SWIG y Java que explica cómo funciona SWIG y cómo envolver C / C ++ con SWIG Java wrappers.

La documentación explica cómo SWIG convierte punteros en C / C ++ en objetos Java opacos, razón por la cual obtienes clases como SWIGTYPE_p_void generadas por SWIG. El problema es que no hay una manera fácil de convertir POJO en estas clases SWIG.

Entonces, por ejemplo, en tensor_c_api.h , el método C TF_CreateTensor() toma un void* que apunta a los datos de entrada y un parámetro size para especificar el tamaño de los datos de entrada en bytes. Este es un patrón de diseño perfectamente razonable para C / C ++, pero completamente absurdo en Java. El método de Java generado por SWIG TF_CreateTensor() toma un objeto SWIGTYPE_p_void como sus datos, junto con size , pero no hay forma de convertir un POJO como un String en un SWIGTYPE_p_void sin escribir mucho código a mano.

Y esta es la encrucijada en la que me encuentro actualmente: escribo una tonelada de métodos de conversión C / C ++ que toman cualquier tipo definido en TF_DataType y lo convierto en void* , o escribo un montón de SWIG typemaps para hacer lo mismo. La documentación de SWIG no parece favorecer ninguna de las soluciones, ya que ambas aparentemente lo hacen indistintamente.

Entonces, la pregunta es, ¿funciones de conversión C / C ++ o mapas tipográficos SWIG?

@kylevedder Veo que estás empezando a entender por qué creé JavaCPP en primer lugar. :)

He estado usando ajustes preestablecidos JavaCPP @saudet 's, de gran utilidad, gracias! Lo estoy usando para construir una interfaz Clojure para tensorflow.

Algunos comentarios:

a) Existe la posibilidad de simplificación / una capa de nivel superior

Gran parte de la API de JavaCPP replica la funcionalidad de protobuf que se puede lograr directamente en la JVM, sin el puente. Me tomó un poco darme cuenta de esto, pero uno simplemente está construyendo un objeto protobuf usando los enlaces JavaCPP, produciendo esta representación independiente de la plataforma usando interoperabilidad y luego metiéndola en la sesión.

Terminé usando protobufs basados ​​en jvm para construir el gráfico directamente, sin pasar por las funciones del constructor JavaCPP. Esto tiene varias ventajas: una API más simple para programar y también un buen formato .toString que muestra el protobuf legible por humanos.

Particularmente para Clojure, es mucho más fácil describir el gráfico de flujo tensorial en términos de estructuras de datos y luego convertirlos directamente a protobuf, que buscar e invocar una función constructora para cada nodo en mi estructura de datos.

b) Mejoras en la construcción y el paquete

No soy experto en la creación de código nativo ni en las herramientas de creación que se utilizan en estos proyectos. Sería genial tener artefactos maven-izados; en particular si también incluían las clases protobuf java generadas. Me tomó una cantidad de tiempo vergonzosa descubrir cómo hacer esto.

c) Sería útil tener una pequeña cantidad de casos de prueba de gráficos para apuntar.

En este momento, mi metodología es algo engorrosa: use las funciones del constructor JavaCPP para generar un gráfico, muévalo a mis protobufs de JVM y vea la forma legible por humanos, y descubra cómo construir mis propios constructores para hacer la misma forma.

Sería útil tener una pequeña colección de gráficos muy simples que ejerciten las funcionalidades centrales de TensorFlow, para que las personas como yo tengan un conjunto razonable de casos de prueba para orientar la interoperabilidad a diferentes idiomas.

De todos modos, gracias por los esfuerzos de todos y sigan con el buen trabajo.

@kovasb ¡ Gracias por los comentarios! Obviamente, queda mucho por hacer para que la interfaz sea más natural para Java, Scala, Clojure, etc.

Si tiene clases auxiliares para integrar la API de C ++ con la API de protobuf de Java, siéntase libre de poner todo eso en el siguiente paquete, incluidas las clases de protobuf de Java generadas, y envíe un PR:
https://github.com/bytedeco/javacpp-presets/tree/master/tensorflow/src/main/java/org/bytedeco/javacpp/helper
Para eso está destinado, y se empaquetará automáticamente en el artefacto Maven, algo que Bazel no parece admitir. En cualquier caso, ¡gracias por investigar esto!

@kovasb Una interfaz clojure suena realmente interesante. ¿Tienes algún código para compartir todavía?

¡Gracias!

Entonces, las personas en este hilo también lo saben, en https://github.com/tensorflow/tensorflow/issues/3 se ha planteado: la diferenciación automática no funciona actualmente a menos que use TF de la api de python. Esto parece una maravilla en espera de que la funcionalidad se transfiera a C ++.

No entiendo muy bien el flujo de datos, pero tal vez sea posible lanzar el material auxiliar de Python junto con la biblioteca de C ++.

Otra solución que estoy viendo es simplemente usar Jpy o uno de los otros puentes (¿alguien tiene recomendaciones?). JyNi también parece bastante interesante pero bastante lejos del horario estelar (aunque sería genial ver más impulso / comunidad detrás de él)

Si JyNi se soluciona, + jython le daría a la JVM una historia realmente impresionante sobre la interoperabilidad del ecosistema python. Uno puede soñar.

+1 para una interfaz Java!

si pudiéramos usar javaCPP, ¿sigue siendo necesario SWIG? ¿Colaboraremos para implementar la interfaz SWIG?

@maxiwu Me gusta pensar que JavaCPP hace un mejor trabajo que SWIG, pero estoy a favor de compararlos para probarlo :)

@kovasb Me interesaría mucho ayudar / contribuir a la interfaz de Clojure.

@sorenmacbeth envíeme un correo electrónico a mi nombre de pila y apellido en gmail, feliz de guiarlos a través de lo que tengo ...

Parece que tenemos aquí un preset de Javacpp bastante completo. ¿Es una solución aceptable para "el equipo"?

@saudet Estoy tratando de crear una copia de los contenedores JavaCPP, pero parece que debido a la rápida tasa de cambio de la fuente de tensorflow, no son compatibles ni con la versión 0.6.0 ni con la rama maestra de hoy. ¿Sería posible actualizarlos con un puntero a la confirmación / versión de tensorflow exacta con la que se probaron?

@nikitakit Acabo de hacer una actualización para la rama maestra aquí: https://github.com/bytedeco/javacpp-presets/commit/43bdcdf03beaaddb4bd5badf5d4f79669e9e78dd

Sin embargo, a diferencia de Caffe, TensorFlow parece tener un lanzamiento cada mes más o menos, así que supongo que comenzaré a estabilizar los enlaces en esos puntos, comenzando con el próximo lanzamiento (¿0.7.0?)

@martinwicke ¿Qué opinas?

Cuando hay un enlace Java estable, estoy feliz de trabajar en la API de Scala.

/ cc @databricks

@kovasb Creo que me perdí esta primera vez. ¿Estás diciendo que toda la magia de diferenciación automática que obtenemos al usar TensorFlow a través de Python se implementa dentro de Python, no dentro de las bibliotecas de C ++? Entonces, en la práctica, ¿una API de Java necesitaría volver a implementar todo esto o sería simplemente otra biblioteca numérica? No estoy lo suficientemente familiarizado con las partes internas de TensorFlow o el pegamento de Python para entender exactamente qué se hace y dónde se hace el trabajo pesado.

@drdozer, eso es lo que entiendo, basado en los comentarios de @girving y luego mirando la fuente un poco yo mismo. Reimplementar cosas en Java parece algo imposible. Sugiero mirar los comentarios en el n. ° 3

Si alguien está realmente interesado, solo recomendaría intentar hacer algunos ejemplos de entrenamiento usando la api de Java (hasta ahora, acabo de ver / hacer la ruta de avance).

Me pregunto hasta dónde llegaríamos ejecutando el código Python con Jython ...

Creo que la capa de la API de Python tiene mucha lógica que la API de la capa de C ++ no expone.
Estaba tratando de seguir la ruta de JavaCpp, pero al final habrá mucho código duplicado y será difícil mantener la coherencia cuando algo cambie en la implementación de Python.

Probablemente el camino más fácil es usar Jython como @saudet había mencionado antes ...

Fue asignado en https://github.com/tensorflow/tensorflow/issues/476 a @ josh11b. Si está trabajando en esto, no tiene sentido usar Jython.

Si usamos jython, ¿seguirá funcionando el código c ++? Estoy buscando usar esto para un servidor que está en Java, pero estoy atascado entre intentar una ruta de Java directamente o simplemente enviar los datos a través de un socket a un proceso de Python

Me gustaría mencionar que aunque la API de Java no incluye muchas características como la diferenciación automática, no he encontrado que esto sea una barrera para mi propio trabajo. Tuve un gran éxito al generar un modelo en Python, serializarlo en un archivo .proto y luego abrirlo a través del contenedor de Java para el entrenamiento. Se puede hacer lo mismo para el tiempo de prueba, ya que creo que la funcionalidad Saver está disponible a través de las API de C ++ y Java.

+1

@saudet
Gracias por crear javacpp y los ajustes preestablecidos para tensorflow. Pude recrear con éxito un gráfico de Python en Java, pero estoy atascado tratando de restaurar desde un archivo de modelo guardado. Esta línea no funciona:

Tensor fn = nuevo Tensor (tensorflow.DT_STRING, nuevo TensorShape (1));
Búfer CharBuffer = fn.createBuffer ();
buffer.put ("modelfile.tf");
session.Run (...);

pero el CharBuffer resulta ser NULL. Si cambio DT_STRING a DT_FLOAT, obtengo un FloatBuffer, pero DT_STRING no parece funcionar.

@nikitakit dijiste que hiciste que esto funcionara. ¿podrías compartir tu código?

@lakshmanok

EDITAR: lo siento, leí mal lo que dijiste aquí. No puedo proporcionar ninguna ayuda para el uso de protectores externos de Java

Como referencia, la parte de mi código que importa gráficos de flujo tensorial está aquí: https://gist.github.com/nikitakit/d3ec270aee9d930267cec3efa844d5aa

Está en Scala, pero la migración a Java / otro lenguaje JVM debería ser sencillo.

Desafortunadamente, mi código para ejecutar nodos en el gráfico está muy relacionado con un marco de Scala que estoy usando, por lo que tendrá que confiar en los documentos de la API de tensorflow para esta parte.

¿Alguien ha llegado a algo con la incrustación del entorno de Python Tensorflow en el JVM? ¿Decir con jython + JyNI? ¿O todo esto es demasiado experimental para trabajar de manera confiable?

Actualmente estoy trabajando en expandir la API de C para agregar soporte para la definición de gráficos. No estoy seguro de cuándo se hará, pero es uno de nuestros objetivos antes de la 1.0.

Estoy trabajando en usar el flujo tensorial de java. Me estoy acercando al problema usando jython y modificando la biblioteca cpython de flujo tensorial para acomodar a otro intérprete de Python. el cpython debería seguir funcionando sin problemas y mi código detecta si el intérprete es Jython y modifica las importaciones / módulos para permitir que funcione. Debajo usa los enlaces javacpp para libtensorflow_cc.so. ¿Es esto algo que el equipo de Google estaría dispuesto a tener en el repositorio oficial? @vrv

Eso parece una buena prueba de concepto, pero creo que un enlace oficial probablemente querría enlazar de forma más nativa que pasar por Python :(

no, en lugar de llamar al contenedor c-python, llamamos al contenedor javaccp. Entonces, sería lo mismo que el flujo del tensor de cpython pero evaluado desde la JVM usando Jython. Reimplementar toda la lógica de Python en otro idioma parece demasiado, terminas con otra API. Los enlaces javacpp le permiten ejecutar Inference sin problemas, pero el modelo debe construirse / entrenarse desde un script cpython en este momento.

¿Alguien ha pensado en hacer que tensorflow funcione con Kotlin? Parece un ajuste más natural y todavía es 100% Java al final del día. Encuentro que el lenguaje Kotlin es un término medio muy agradable entre Python y Java puro.

Actualización: pude hacer que las cosas funcionen con éxito con javacpp (gracias a @saudet ) y hacer que los programas Java lean / ejecuten modelos de TensorFlow.

https://medium.com/google-cloud/how-to-invoke-a-trained-tensorflow-model-from-java-programs-27ed5f4f502d#.tx8nyds5v

Gracias @lakshmanok y @saudet . El proyecto javacpp parece implementar la mayoría de las API de TensorFlow. Estamos intentando ejecutar el tensorflow / servicio en Java.

La API es simple y está definida por protobuf . Ahora hemos implementado el servidor y queremos implementar el cliente en Java. Solo necesita construir la TensorProto en Java e invocar la llamada gRPC . TensorFlow proporciona funciones auxiliares para convertir matrices de múltiples dimensiones para Python y C ++, pero no para Java.

¿Puede decirnos cómo usar javacpp o implementarlo nosotros mismos para esto?

Lo que está buscando probablemente ya esté en https://github.com/bytedeco/javacpp-presets/blob/master/tensorflow/src/main/java/org/bytedeco/javacpp/helper/tensorflow.java pero avíseme si falta algo allí. ¡Gracias!

¿Todavía se está trabajando en esto? ¿Existe un repositorio oficial de github para este proyecto de portabilidad? Veo un par de repositorios aleatorios, pero no puedo decirlo.

Sí, pero probablemente en algún momento de octubre / noviembre. Estamos usando la API de C en lugar de SWIGing a la API de C ++. Mientras tanto, puede utilizar las vinculaciones que mencionó saudet.

¿Cómo llegó a la conclusión de utilizar la API de C? estamos trabajando en un
interfaz ruby ​​usando trago:
http://github.com/somaticio/tensorflow.rb

El martes 13 de septiembre de 2016 a las 6:22 p.m., Jonathan Hseu [email protected]
escribió:

Sí, pero probablemente en algún momento de octubre / noviembre. Estamos usando la API de C
en lugar de SWIGing a la API de C ++. Mientras tanto, puede utilizar el
fijaciones que saudet mencionó.

-
Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/tensorflow/tensorflow/issues/5#issuecomment -246844192,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAA5v3g86Z6D1rz-aTGdMyMWnQZhrZUYks5qpyIJgaJpZM4Getd8
.

En el futuro, preferiríamos que todos los enlaces de idioma usen la API de C. Próximamente se publicará un documento.

Puede ver el uso de ejemplo aquí:
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/go

Sin embargo, no hay urgencia, y construir sobre SWIG está bien por ahora.

@jhseu ¿

Vaya, gran cambio. Ojalá esto se decidiera antes. De todos modos para ver los documentos
¿cuanto antes?

El miércoles 14 de septiembre de 2016 a las 5:56 p.m., Samuel Audet [email protected]
escribió:

@jhseu https://github.com/jhseu ¿ Eso significa que la API C será
expandido para cubrir todo lo que los enlaces de Python tienen acceso actualmente?

-
Estás recibiendo esto porque hiciste un comentario.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/tensorflow/tensorflow/issues/5#issuecomment -247167887,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAA5vwfBJoZC2s33_7E9Xy6-NYNUjHjnks5qqG2FgaJpZM4Getd8
.

@saudet La mayoría de las funciones, excepto a corto plazo,
@jtoy No es urgente que

Los documentos solo describen cómo hacerlo y las convenciones de nomenclatura. Sin embargo, puede comenzar a migrar a la API C sin ellos:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/c/c_api.h

Gracias @saudet . Encontré esto en stackoverflow sobre la generación de TensorProto con API protobuf pura. Y aquí está el código de

@ tobegit3hub Bien , si puede hacer que esto funcione con la API de C ++, agréguelo al paquete auxiliar de JavaCPP Presets y envíe una solicitud de extracción. Este chico estaría interesado en algo así: https://github.com/bytedeco/javacpp-presets/issues/240

@girving ¿Javacpp ya resuelve el problema?
Quiero contribuir a tensorflow java api, prefiero implementarlo como python.

Hola chicos, ¿alguien ya comenzó a trabajar en los enlaces de lenguaje Java / Scala usando la API de C?
(en lugar de construir sobre SWIG)

Tengo una interfaz Java / Scala que funciona para tensorflow usando solo la API C a través de JNR . Desafortunadamente, todavía no tengo permiso para abrirlo. Publicaré aquí cuando lo publique. Todavía es un trabajo en progreso, pero es muy funcional.

@jdolson ¿La API que expone acepta los objetos de búfer de protocolo de TensorFlow? Uno de los mayores problemas que he tenido al usar los ajustes preestablecidos de javacpp de

@Intropy Sí, compilo todas las fuentes *.proto tensorflow protoc y uso esas clases en la API.

@jhseu ¿La interfaz C API todavía está en camino de ser lanzada en algún momento de noviembre? Si no es así, ¿cuál es el estado actual?

@eaplatanios : La API de C es en su mayor parte estable (y lo será oficialmente en 1.0) y se puede usar aunque no esté completa (aún falta la capacidad de degradar automáticamente los cálculos al gráfico). En https://www.tensorflow.org/how_tos/language_bindings/index.html se encuentra un documento que describe cómo se puede usar la API de C para crear enlaces de idioma.

La API de Go se implementó utilizando la API de C como primer ejemplo de seguir el documento anterior.

Esperamos que los enlaces de Java se construyan sobre esto también (usando JNI) y hemos comenzado a explorar eso un poco. Cualquier comentario / aprendizajes personas han basado en el uso de @saudet 's maravilloso trabajo con conseguir JavaCPP de trabajo sería bueno conocer.

Tengo algunas sugerencias basadas en el uso de enlaces JavaCPP.

Primero, dado que los búferes de protocolo se compilan directamente en Java, se deben usar las versiones de Java. Preferiblemente, creo que los búferes de protocolo que participan en la API deberían estar disponibles por separado como un módulo maven y deberían venir con las protodefiniciones para que las personas en una pila de Java tengan una manera fácil de obtener las definiciones tanto binarias como sencillas. manera de obtener las definiciones de proto para su inclusión dentro de otras definiciones de proto.

En segundo lugar, sería útil encontrar la versión mínima de libc que TensorFlow necesita y construir en función de eso.

En tercer lugar, es mucho más fácil utilizar una API cuidadosamente diseñada que una generada automáticamente. Sé que eso es obvio y parece una oportunidad para JavaCPP. No es mi intención que lo sea. Estoy muy contento de que exista la interfaz generada automáticamente. Es utilizable. Pero requiere circunloquios extraños, tiene muchas verrugas y es bastante difícil leer el código para descubrir cómo hacer lo que estás tratando de hacer. Desearía que esta sugerencia fuera más útil que "debería hacerlo bien", pero supongo que el punto es que mire cuán diferentes son la API de C ++ y la API de Python. Ambos son sencillos porque se ajustan a su entorno de una manera que es poco probable que coincida con el código convertido automáticamente.

Quizás hubiera sido mejor admitir el backend C de Swig y generar TF C API a través de Swig también: https://github.com/swig/swig/issues/800 para que otros lenguajes como Go, Ruby, R puedan usar el C api para escribir sus propios enlaces.

Tenemos una API C existente para agregar soporte para cualquier idioma con C FFI:
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/c/c_api.h

(Y eso es lo que se utiliza para crear los enlaces de Go, Java, Rust, etc. para TensorFlow)

¿Se puede acceder a la API de C mediante JNA ?

@jhseu Me refiero a que tal vez podría haberse generado a partir de la API de C ++ antes, antes de implementar manualmente la API de C.

@ Quantum64 , aquí hay un enlace de Scala de tensorflow que usa JNA.

Dado que este problema aún está abierto, ¿cómo
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/java
siendo implementado y cuál fue el RP para el compromiso?

@hsaputra : ¿Podría tensorflow/java , la mayoría de las cuales se mencionan en este número (como 2b1cd28, d73a266 y muchas otras en el medio)

Hola @asimshankar , gracias por la respuesta.

Me pregunto cuál fue la ruta que tomó tensorflow/java para implementar la API de Java, ya que este ticket no está cerrado.
Hubo discusiones sobre el uso de JavaCPP vs SWIG vs call a través de Jython.

¿Parece que tensorflow/java se implementa con JNI directo para llamar a las API de C en su lugar?

Correcto.

Oye,

Acabo de hacer funcionar estas fijaciones Swig ayer. Tengo una solicitud para un cambio de API. Actualmente, para generar tensores, se requiere reflexión y el formato de las matrices es un poco poco atractivo, ya que requieren el uso de matrices Java nativas n-dimensionales. ¿Podemos mantener esta interfaz, pero también agregar algunos métodos para crear tensores que requieren matrices unidimensionales y especificar la forma usando otra matriz de long? Me imagino que podría verse así:

double[] matrix = {1.414, 2.718, 3.1415, 3.4, 56.7, 89.0};
long[] shape = {2, 3};

// add a method for each primitive type
org.tensorflow.Tensor tensor = org.tensorflow.Tensor.createDouble(matrix, shape);

Esto también conduciría a la posibilidad de crear tensores int8, int16, uint8, uint16, uint32, lo que ayudará con la compatibilidad.

¿Debería convertir esto en un problema? ¿O está bien aquí?

Además, estoy más que feliz de intentar desarrollar estos métodos.

@hollinwilkins : Espero que PR # 6577 aborde esto, con solo un ligero ajuste a su método de fábrica propuesto:

Tensor tensor = Tensor.create(shape, DoubleBuffer.wrap(matrix));

@asimshankar ¡ Esto es genial! Gracias por la rápida respuesta. Parece que también está bastante cerca de fusionarse: +1:

Estoy tratando de usar la nueva API de Java y me he encontrado con algunas cosas que lo hacen más difícil de usar de lo que creo que debería ser:

  1. La API de Java debería aceptar un objeto GraphDef. Actualmente solo acepta una matriz de bytes que representa el binario serializado del búfer del protocolo GraphDef. Es extraño requerir un paso de serialización / deserialización en el límite de la biblioteca.
  2. Session.Runner.feed debería poder aceptar org.tensorflow.framework.TensorProto o debería haber una buena forma de crear org.tensorflow.Tensor desde org.tensorflow.framework.TensorProto.
  3. Session.Runner.run devuelve una lista de objetos Tensor. De manera similar a lo anterior, debería haber una manera fácil de obtener la salida de TensorProto, ya sea directamente o dando a org.tensorflow.Tensor una buena manera de convertir a TensorProto.
  4. Session.Runner.run se traga el estado. Debería haber una forma de obtener esa información sobre fallas, tal vez lanzando una excepción.

Además, es posible que me perdí la forma de manejar esto, pero me parece que no puedo obtener todos los tipos de tensor admitidos en la salida de la ejecución. Por ejemplo, si mi tensor de salida es de tipo INT16, entonces no hay forma de extraer el valor de él. No hay Tensor.shortValue o similar, y Tensor.intValue parece requerir una coincidencia exacta. Estoy basando esto en leer DEFINE_GET_SCALAR_METHOD en tensor_jni.cc.

@Intropy : Gracias por tus comentarios y definitivamente tienen sentido. Por ahora puedo compartir algunos pensamientos con ustedes:

RE: protobufs: En este punto, estamos tratando de mantener la API central independiente de protobufs por varias razones (incluido el uso en sistemas con recursos limitados donde algo como nanproto puede ser más apropiado). Entonces, esa es la razón por la que hemos dudado, pero es algo en lo que estamos pensando y se agradecen las sugerencias. Una posibilidad es tener toda la funcionalidad relacionada con protobuf en un paquete separado para que haya una separación clara.

Entonces, volviendo a sus puntos:

  1. Véase más arriba. Sin embargo, apuesto a que hay muchos casos en los que byte[] tiene más sentido (como leer el gráfico de un archivo o canal de red)

  2. Punto a favor

  3. Véase más arriba.

  4. Session.runner.run no debería ser un estado de ingestión. Si hay un error, se lanzará una excepción ( session_jni.cc:166 ). Si eso no sucede, presente un error.

Tiene razón, no todos los tipos son compatibles todavía, pero debería ser bastante fácil de agregar. Si tiene una necesidad urgente de los tipos que faltan, no dude en presentar un problema y / o enviar un PR. Las contribuciones son bienvenidas :)

@asimshankar Gracias por tus pensamientos.

En cuanto al primer punto, no es gran cosa. Como dices, hay ocasiones en las que un byte [] tiene más sentido. En mi propio caso de uso, tengo un InputStream, que es trivial de convertir a byte []. La API de búfer de protocolo simplifica la conversión. Solo considero que el byte [] es una verruga en la API porque de todos modos tendrás que deserializar (en TF_GraphImportGraphDef) y de esta manera perderás algo de seguridad de tipos. También hay que considerar la serialización json de proto3.

Sobre el estado de deglución, tienes razón. Me perdí la excepción sin marcar.

La forma más obvia de manejar 2 y 3 es darle a org.tensorflow.Tensor una fábrica que convierte de un TensorProto y algo a TensorProto (). Si los casos de uso de recursos limitados son el problema con los búferes de protocolo, entonces las personas en esas circunstancias simplemente no podrían usar esas funciones. El problema es que las personas que usan esas funciones pagarían el costo de una conversión que probablemente podría evitarse si el Tensor almacena sus datos directamente en un protobuff. Nunca había trabajado con jni antes, así que tengo problemas para seguir cómo se almacenan los datos, pero parece que esencialmente trata a nativeHandle como un puntero a un TF_Tensor que tiene un TensorBuffer que se trata esencialmente como un vacío de tamaño *.

¿Podríamos resolver este problema y presentar problemas separados para cada función en la interfaz de Java? Facilitará el seguimiento / análisis, luego podemos cerrar este problema.

@drpngx : Mi intención es obtener un par de cambios más en (leer tensores de búferes) antes de cerrar esto como hicimos para Go y que las características / errores se archiven individualmente. Esperemos que pronto.

¡Suena bien. Gracias!

Muy bien, parece que tenemos suficiente base para construir (por ejemplo, suficiente para construir el ejemplo de LabelImage y la gente está presentando errores / solicitudes de funciones más específicas.

Voy a cerrar este tema. Todavía hay mucho por hacer en la API de Java, pero analicemos / rastreemos eso en temas separados. ¡Gracias!

@asimshankar estamos en el proceso de seleccionar el marco de aprendizaje profundo (mxnet / tf) y nuestro etl / api se basan en el flujo de spark / akka ... ¿Hay un plan para agregar soporte de tiempo de ejecución distribuido a la API de Java para ejecutar el entrenamiento en paralelo del modelo usando ps nodos? El nodo ps es fundamental para nosotros para muchos casos de uso ... los ajustes preestablecidos de javacpp pueden ser más fáciles de exportar para el primer corte, ya que la API de C en sí no parece tener distribuido_runtime ...

@ debasish83 : Incluir el tiempo de ejecución distribuido por sí mismo es trivial, pero hay un montón de construcciones de nivel superior en la API de Python como la clase Estimator que se encarga de un montón de cosas (puntos de control, ahorro de resumen, etc. hacer que la visualización a través de TensorBoard sea trivial) que puede hacer que sea más adecuado para ejecutar los trabajos de entrenamiento en Python.

Todo esto se puede construir utilizando las primitivas existentes en la API de Java, pero el enfoque correcto dependerá de sus necesidades precisas.

¿Quizás deberíamos sincronizar fuera del hilo?

@asimshankar ¿Existe ya una forma desde el enlace de Java de tensorflow para recuperar información de un graphDef (construido a partir del archivo .pb del gráfico en el disco) como la lista de nodos, formato de entrada y salida o es una característica entrante? ¡Gracias!

@asimshankar No estoy seguro de entender lo que falta para entrenar con TF Java. ¿Es un problema de la biblioteca numérica (falta un número)? Quiero decir, si no está interesado en la visualización de datos de TensorBoard, pero solo está entrenando, utilizando una biblioteca numérica nativa de Java, ¿por qué usar Python solo para el entrenamiento (como sugiere sobre la clase Estimator )?

Gracias.

¿Cuál es el estado de los modelos de entrenamiento en Java? He estado pensando en escribir un complemento ImageJ (paquete de análisis de imágenes popular y gratuito) para aplicar enfoques como https://arxiv.org/pdf/1505.04597.pdf (recientemente muy popular en la segmentación de imágenes para seguimiento celular y aplicaciones biomédicas). Creo que sería útil proporcionar una gama de modelos previamente entrenados y permitir a los usuarios refinarlos para su caso de uso específico. He estado investigando DL4J para este propósito. ¿Hay planes concretos para permitir la instalación de los enlaces TF Java?

@bergwerf : La formación en Java es ciertamente posible, si no especialmente conveniente.
Puede encontrar una muestra en https://github.com/tensorflow/models/tree/master/samples/languages/java/training

(Además, estoy seguro de que lo sabe, pero consulte también https://imagej.net/TensorFlow)

¡Oh, genial! Entonces, mi información debe estar desactualizada ;-). Pensé que había leído
en algún lugar, la API de Java solo estaba destinada a predecir con
modelos. Examinaré el ejemplo.

El miércoles 28 de marzo de 2018 a las 22:01, Asim Shankar [email protected] escribió:

@bergwerf https://github.com/bergwerf : La formación en Java es sin duda
posiblemente, si no es particularmente conveniente.
Puede encontrar una muestra en
https://github.com/tensorflow/models/tree/master/samples/languages/java/training

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/tensorflow/tensorflow/issues/5#issuecomment-377015867 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AEQJ1UD9-xACQAII5996ees_UFJ_NzL-ks5ti-wSgaJpZM4Getd8
.

@asimshankar eso es increíble 👍 💯 🥇, voy a agregar a mi repositorio https://github.com/loretoparisi/tensorflow-java

¿Fue útil esta página
0 / 5 - 0 calificaciones