
Hola a todos! Hoy hablaremos sobre la implementación del aprendizaje automático en Scala. Comenzaré explicando cómo llegamos a esa vida. Por lo tanto, nuestro equipo durante mucho tiempo utilizó todas las características del aprendizaje automático en Python. Es conveniente, hay muchas bibliotecas útiles para la preparación de datos, una buena infraestructura para el desarrollo, me refiero a Jupyter Notebook. Todo estaría bien, pero ante el problema de paralelizar los cálculos en la producción, y decidió usar Scala en el producto. Por qué no, pensamos, hay toneladas de bibliotecas, ¡incluso Apache Spark está escrito en Scala! Al mismo tiempo, hoy desarrollamos modelos en Python, y luego repetimos el entrenamiento en Scala para una mayor serialización y uso en la producción. Pero, como dicen, el diablo está en los detalles.
Inmediatamente quiero aclarar, querido lector, que este artículo no fue escrito para socavar la reputación de Python para el aprendizaje automático. No, el objetivo principal es abrir la puerta al mundo del aprendizaje automático en Scala, dar una breve descripción del enfoque alternativo que se desprende de nuestra experiencia y decirle qué dificultades encontramos.
En la práctica, resultó que no todo fue tan alegre: no hay muchas bibliotecas que implementen los algoritmos clásicos de aprendizaje automático, y las que a menudo son proyectos OpenSource sin el apoyo de grandes proveedores. Sí, por supuesto, existe Spark MLib, pero está fuertemente ligado al ecosistema Apache Hadoop, y realmente no quería arrastrarlo a la arquitectura de microservicios.
¡Lo que se necesitaba era una solución que salvara al mundo y devolviera un sueño reparador, y se encontró!
Que necesitas
Cuando elegimos una herramienta para el aprendizaje automático, procedemos de los siguientes criterios:
- debería ser simple;
- a pesar de su simplicidad, nadie ha cancelado una amplia funcionalidad;
- Tenía muchas ganas de poder desarrollar modelos en el intérprete web, y no a través de la consola o de conjuntos y compilaciones constantes;
- la disponibilidad de documentación juega un papel importante;
- idealmente, habría soporte al menos para responder problemas de github.
Que vimos
- Apache Spark MLib : no nos convenía . Como se mencionó anteriormente, este conjunto de bibliotecas está fuertemente vinculado a la pila de Apache Hadoop y al propio Spark Core, que pesa demasiado para construir microservicios basados en él.
- Apache PredictionIO : un proyecto interesante, muchos colaboradores, hay documentación con ejemplos. De hecho, este es un servidor REST en el que los modelos están girando. Hay modelos listos, por ejemplo, clasificación de texto, cuyo lanzamiento se describe en la documentación. La documentación describe cómo puede agregar y entrenar sus modelos. No encajamos, ya que Spark se usa debajo del capó, y esto es más del área de una solución monolítica, en lugar de una arquitectura de microservicio.
- Apache MXNet : un marco interesante para trabajar con redes neuronales, hay soporte para Scala y Python; esto es conveniente, puede entrenar una red neuronal en Python y luego cargar el resultado guardado de Scala al crear una solución de producción. Lo usamos en soluciones de producción, hay un artículo separado sobre esto aquí .
- Sonrisa : muy similar al paquete scikit-learn para Python. Hay muchas implementaciones de algoritmos clásicos de aprendizaje automático, buena documentación con ejemplos, soporte en github, un visualizador integrado (desarrollado por Swing), puede usar Jupyter Notebook para desarrollar modelos. ¡Esto es justo lo que necesitas!
Preparación del medio ambiente
Entonces, elegimos Smile. Te diré cómo ejecutarlo en Jupyter Notebook usando el algoritmo de agrupación k-means como ejemplo. Lo primero que debemos hacer es instalar Jupyter Notebook con soporte de Scala. Esto se puede hacer a través de pip, o usar una imagen Docker ya ensamblada y configurada. Estoy por una segunda opción más simple.
Para hacer amigos de Jupyter con Scala, quería usar BeakerX, que es parte de la imagen de Docker, disponible en el repositorio oficial de BeakerX. Esta imagen se recomienda en la documentación de Smile, y puede ejecutarla así:
Pero aquí estaba esperando el primer problema: al momento de escribir el artículo, BeakerX 1.0.0 estaba instalado dentro de la imagen beakerx / beakerx, y la versión 1.4.1 ya estaba disponible en el github oficial del proyecto (más precisamente, la última versión 1.3.0, pero el asistente contiene 1.4.1, y funciona :-)).
Está claro que quiero trabajar con la última versión, así que armé mi propia imagen basada en BeakerX 1.4.1. No te aburriré con el contenido del Dockerfile, aquí hay un
enlace a él.
Por cierto, para aquellos que usarán mi imagen, habrá una pequeña ventaja: en el directorio de ejemplos hay un ejemplo de k-medias para una secuencia aleatoria con trazado (esto no es una tarea completamente trivial para los cuadernos Scala).
Descargar Smile in Jupyter Notebook
Excelente ambiente preparado! Creamos nuevos cuadernos Scala en una carpeta en nuestro directorio, luego necesitamos descargar las bibliotecas de Maven para que la Sonrisa funcione.
%%classpath add mvn com.github.haifengl smile-scala_2.12 1.5.2
Después de ejecutar el código, aparecerá una lista de archivos jar descargados en su bloque de salida.
Siguiente paso: importar los paquetes necesarios para que el ejemplo funcione.
import java.awt.image.BufferedImage import java.awt.Color import javax.imageio.ImageIO import java.io.File import smile.clustering._
Preparación de datos para la agrupación
Ahora resolveremos el siguiente problema: generar una imagen que consista en zonas de tres colores primarios: rojo, verde y azul (R, G, B). Uno de los colores en la imagen prevalecerá. Agrupamos los píxeles de la imagen, tomamos el grupo en el que habrá más píxeles, cambiamos su color a gris y construimos una nueva imagen a partir de todos los píxeles. Resultado esperado: la zona del color predominante se volverá gris, el resto de la zona no cambiará su color.
Como resultado de ejecutar este código, se muestra la siguiente imagen:

Siguiente paso: convierte la imagen en un conjunto de píxeles. Por píxel nos referimos a una entidad con las siguientes propiedades:
- coordenada lateral ancha (x);
- coordenada lateral estrecha (y);
- valor de color;
- valor opcional del número de clase / clúster (antes de que se complete el agrupamiento, estará vacío).
Como entidad, es conveniente usar la
case class
:
case class Pixel(x: Int, y: Int, rgbArray: Array[Double], clusterNumber: Option[Int] = None)
Aquí, para los valores de color, se
rgbArray
matriz
rgbArray
de tres valores de rojo, verde y azul (por ejemplo, para la
Array(255.0, 0, 0)
color rojo
Array(255.0, 0, 0)
).
Esto completa la preparación de datos.
Agrupación de colores de píxeles
Entonces, tenemos una colección de píxeles de tres colores primarios, por lo que agruparemos los píxeles en tres clases.
La documentación recomienda establecer el parámetro de ejecución en el rango de 10 a 20.
Cuando se ejecuta este código, se
KMeans
un objeto de tipo
KMeans
. El bloque de salida contendrá información sobre los resultados de la agrupación:
K-Means distortion: 0.00000 Clusters of 230400 data points of dimension 3: 0 50813 (22.1%) 1 51667 (22.4%) 2 127920 (55.5%)
Un clúster contiene más píxeles que el resto. Ahora necesitamos marcar nuestra colección de píxeles con clases del 0 al 2.
Repintar imagen
Lo único que queda es seleccionar el clúster con el mayor número de píxeles y volver a pintar todos los píxeles incluidos en este clúster a gris (cambie el valor de la matriz
rgbArray
).
No hay nada complicado, solo agrupar por número de grupo (esta es nuestra
Option:[Int]
), contar el número de elementos en cada grupo y extraer el grupo con el número máximo de elementos. A continuación, cambie el color a gris solo para los píxeles que pertenecen al clúster encontrado.
Crea una nueva imagen y guarda los resultados.
Recopilación de una nueva imagen de la colección de píxeles:
Eso es lo que, al final, hicimos.

Guardamos ambas imágenes.
ImageIO.write(testImage, "png", new File("testImage.png")) ImageIO.write(modifiedImage, "png", new File("modifiedImage.png"))
Conclusión
El aprendizaje automático en Scala existe. Para implementar los algoritmos básicos, no es necesario arrastrar una gran biblioteca. El ejemplo anterior muestra que durante el desarrollo no puede renunciar a los medios habituales, el mismo Jupyter Notebook puede hacerse fácilmente amigo de Scala.
Por supuesto, para una descripción completa de todas las características de Smile, un artículo no es suficiente, y esto no se incluyó en los planes. La tarea principal, abrir la puerta al mundo del aprendizaje automático en Scala, creo que se ha completado. ¡Depende de ti usar estas herramientas, y aún más, arrastrarlas a producción o no!
Referencias