
Olá pessoal! Hoje falaremos sobre a implementação do aprendizado de máquina no Scala. Vou começar explicando como chegamos a essa vida. Portanto, nossa equipe utilizou por muito tempo todos os recursos do aprendizado de máquina no Python. É conveniente, existem muitas bibliotecas úteis para preparação de dados, uma boa infra-estrutura para o desenvolvimento, quero dizer, o Jupyter Notebook. Tudo ficaria bem, mas confrontado com o problema de paralelizar cálculos na produção, e decidiu usar Scala no produto. Por que não, pensamos, existem toneladas de bibliotecas por aí, até o Apache Spark está escrito em Scala! Ao mesmo tempo, hoje desenvolvemos modelos em Python e repetimos o treinamento no Scala para posterior serialização e uso na produção. Mas, como se costuma dizer, o diabo está nos detalhes.
Imediatamente, quero esclarecer, caro leitor, que este artigo não foi escrito para minar a reputação do Python de aprendizado de máquina. Não, o objetivo principal é abrir as portas para o mundo do aprendizado de máquina no Scala, fornecer uma breve visão geral da abordagem alternativa que se segue da nossa experiência e explicar as dificuldades que encontramos.
Na prática, verificou-se que não era tão divertido: não existem muitas bibliotecas que implementam os algoritmos clássicos de aprendizado de máquina e aquelas que são geralmente projetos de código aberto sem o apoio de grandes fornecedores. Sim, é claro, existe o Spark MLib, mas está fortemente ligado ao ecossistema Apache Hadoop, e eu realmente não queria arrastá-lo para a arquitetura de microsserviço.
O que era necessário era uma solução que salvasse o mundo e traria de volta um sono reparador, e foi encontrada!
Do que você precisa?
Quando escolhemos uma ferramenta para aprendizado de máquina, partimos dos seguintes critérios:
- deveria ser simples;
- apesar de sua simplicidade, ninguém cancelou uma ampla funcionalidade;
- Eu realmente queria ser capaz de desenvolver modelos no intérprete da Web, e não através do console ou montagens e compilações constantes;
- a disponibilidade da documentação desempenha um papel importante;
- idealmente, haveria suporte pelo menos para responder a problemas do github.
O que vimos?
- Apache Spark MLib : não nos convinha . Como mencionado acima, esse conjunto de bibliotecas está fortemente vinculado à pilha do Apache Hadoop e ao próprio Spark Core, que pesa demais para criar microsserviços com base nela.
- Apache PredictionIO : um projeto interessante, muitos colaboradores, há documentação com exemplos. De fato, este é um servidor REST no qual os modelos estão girando. Existem modelos prontos, por exemplo, classificação de texto, cujo lançamento está descrito na documentação. A documentação descreve como você pode adicionar e treinar seus modelos. Não nos encaixamos, pois o Spark é usado sob o capô, e isso é mais da área de uma solução monolítica do que de uma arquitetura de microsserviço.
- Apache MXNet : uma estrutura interessante para trabalhar com redes neurais, há suporte para Scala e Python - isso é conveniente, você pode treinar uma rede neural em Python e carregar o resultado salvo do Scala ao criar uma solução de produção. Nós o usamos em soluções de produção, há um artigo separado sobre isso aqui .
- Sorriso : muito semelhante ao pacote scikit-learn para Python. Existem muitas implementações de algoritmos clássicos de aprendizado de máquina, boa documentação com exemplos, suporte no github, um visualizador integrado (desenvolvido pela Swing), você pode usar o Jupyter Notebook para desenvolver modelos. Isto é exatamente o que você precisa!
Preparação do ambiente
Então, escolhemos o Smile. Vou explicar como executá-lo no Jupyter Notebook usando o algoritmo de agrupamento k-means como exemplo. A primeira coisa que precisamos fazer é instalar o Jupyter Notebook com suporte para Scala. Isso pode ser feito via pip ou use uma imagem do Docker já montada e configurada. Sou a favor de uma segunda opção mais simples.
Para fazer Jupyter fazer amizade com Scala, eu queria usar o BeakerX, que faz parte da imagem do Docker, disponível no repositório oficial do BeakerX. Esta imagem é recomendada na documentação do Smile e você pode executá-la assim:
Mas aqui estava o primeiro problema: no momento da redação do artigo, o BeakerX 1.0.0 estava instalado na imagem beakerx / beakerx e a versão 1.4.1 já estava disponível no github oficial do projeto (mais precisamente, na versão mais recente 1.3.0, mas o assistente contém 1.4.1, e funciona :-)).
Está claro que eu quero trabalhar com a versão mais recente, então montei minha própria imagem com base no BeakerX 1.4.1. Não vou aborrecê-lo com o conteúdo do Dockerfile, aqui está um
link para ele.
A propósito, para aqueles que usarão minha imagem, haverá um pequeno bônus: no diretório de exemplos, existe um exemplo de k-means para uma sequência aleatória com plotagem (essa não é uma tarefa completamente trivial para os notebooks Scala).
Faça o download do Smile in Jupyter Notebook
Excelente ambiente preparado! Criamos um novo bloco de notas Scala em uma pasta em nosso diretório e precisamos fazer o download das bibliotecas do Maven para que o Smile funcione.
%%classpath add mvn com.github.haifengl smile-scala_2.12 1.5.2
Após executar o código, uma lista de arquivos jar baixados aparecerá em seu bloco de saída.
Próxima etapa: importando os pacotes necessários para o exemplo funcionar.
import java.awt.image.BufferedImage import java.awt.Color import javax.imageio.ImageIO import java.io.File import smile.clustering._
Preparando Dados para Armazenamento em Cluster
Agora vamos resolver o seguinte problema: gerar uma imagem composta por zonas de três cores primárias - vermelho, verde e azul (R, G, B). Uma das cores na imagem prevalecerá. Agrupamos os pixels da imagem, pegamos o cluster no qual haverá mais pixels, alteramos sua cor para cinza e construímos uma nova imagem a partir de todos os pixels. Resultado esperado: a zona da cor predominante ficará cinza, o restante da zona não mudará de cor.
Como resultado da execução desse código, a seguinte imagem é exibida:

Próxima etapa: converta a imagem em um conjunto de pixels. Por pixel, entendemos uma entidade com as seguintes propriedades:
- coordenada lateral larga (x);
- coordenada lateral estreita (y);
- valor da cor;
- valor opcional do número de classe / cluster (antes que o cluster seja concluído, ele estará vazio).
Como entidade, é conveniente usar a
case class
:
case class Pixel(x: Int, y: Int, rgbArray: Array[Double], clusterNumber: Option[Int] = None)
Aqui, para os valores de cor, é
rgbArray
matriz
rgbArray
de três valores de vermelho, verde e azul (por exemplo, para a
Array(255.0, 0, 0)
cor vermelha
Array(255.0, 0, 0)
).
Isso completa a preparação dos dados.
Cluster de cores de pixel
Portanto, temos uma coleção de pixels de três cores primárias, portanto, agruparemos os pixels em três classes.
A documentação recomenda definir o parâmetro de
runs
no intervalo de 10 a 20.
Quando esse código é executado, um objeto do tipo
KMeans
será criado. O bloco de saída conterá informações sobre os resultados do armazenamento em cluster:
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%)
Um cluster contém mais pixels que o restante. Agora precisamos marcar nossa coleção de pixels com classes de 0 a 2.
Repintar imagem
A única coisa que resta é selecionar o cluster com o maior número de pixels e repintar todos os pixels incluídos neste cluster para cinza (altere o valor da matriz
rgbArray
).
Não há nada complicado, basta agrupar por número de cluster (esta é a nossa
Option:[Int]
), contar o número de elementos em cada grupo e retirar o cluster com o número máximo de elementos. Em seguida, altere a cor para cinza apenas para os pixels que pertencem ao cluster encontrado.
Crie uma nova imagem e salve os resultados.
Reunindo uma nova imagem da coleção de pixels:
Foi o que, no final, fizemos.

Nós salvamos as duas imagens.
ImageIO.write(testImage, "png", new File("testImage.png")) ImageIO.write(modifiedImage, "png", new File("modifiedImage.png"))
Conclusão
O aprendizado de máquina no Scala existe. Para implementar os algoritmos básicos, não é necessário arrastar uma grande biblioteca. O exemplo acima mostra que, durante o desenvolvimento, você não pode desistir dos meios usuais, o mesmo Notebook Jupyter pode ser facilmente amigo de Scala.
Obviamente, para uma visão geral completa de todos os recursos do Smile, um artigo não é suficiente e isso não foi incluído nos planos. A principal tarefa - abrir a porta para o mundo do aprendizado de máquina no Scala - acho que está concluída. A decisão de usar essas ferramentas e, mais ainda, arrastá-las para a produção, depende de você!
Referências