O artigo Aprendizado de máquina para desenvolvedores Java: algoritmos para aprendizado de máquina introduziu a configuração de um algoritmo de aprendizado de máquina e o desenvolvimento de uma função de previsão em Java. Os leitores aprenderam o funcionamento interno de um algoritmo de aprendizado de máquina e percorreram o processo de desenvolvimento e treinamento de um modelo. Este artigo continua de onde aquele parou. Você terá uma rápida introdução ao Weka, uma estrutura de aprendizado de máquina para Java. Em seguida, você verá como configurar um pipeline de dados de machine learning, com um processo passo a passo para levar seu modelo de machine learning do desenvolvimento à produção. Também discutiremos brevemente como usar contêineres Docker e REST para implantar um modelo de ML treinado em um ambiente de produção baseado em Java.
O que esperar deste artigo
Implantar um modelo de aprendizado de máquina não é o mesmo que desenvolver um. Estas são partes diferentes do ciclo de vida de desenvolvimento de software e muitas vezes implementadas por equipes diferentes. O desenvolvimento de um modelo de aprendizado de máquina requer a compreensão dos dados subjacentes e um bom domínio de matemática e estatística. A implantação de um modelo de aprendizado de máquina na produção normalmente é um trabalho para alguém com experiência em engenharia de software e operações.
Este artigo é sobre como disponibilizar um modelo de aprendizado de máquina em um ambiente de produção altamente escalonável. Presume-se que você tenha alguma experiência em desenvolvimento e um conhecimento básico de modelos e algoritmos de aprendizado de máquina; caso contrário, você pode começar lendo Aprendizado de máquina para desenvolvedores Java: Algoritmos para aprendizado de máquina.
Vamos começar com uma rápida atualização sobre o aprendizado supervisionado, incluindo o aplicativo de exemplo que usaremos para treinar, implantar e processar um modelo de aprendizado de máquina para uso em produção.
Aprendizado de máquina supervisionado: uma atualização
Um modelo simples e supervisionado de aprendizado de máquina ilustrará o processo de implantação de ML. O modelo mostrado na Figura 1 pode ser usado para prever o preço de venda esperado de uma casa.
Lembre-se de que um modelo de aprendizado de máquina é uma função com parâmetros internos que podem ser aprendidos que mapeiam entradas em saídas. No diagrama acima, uma função de regressão linear, hθ(x), é usado para prever o preço de venda de uma casa com base em vários recursos. O x variáveis da função representam os dados de entrada. O θ variáveis (teta) representam os parâmetros internos do modelo que podem ser aprendidos.
Para prever o preço de venda de uma casa, você deve primeiro criar uma matriz de dados de entrada de x variáveis. Essa matriz contém características como o tamanho do lote ou o número de cômodos de uma casa. Essa matriz é chamada de vetor de recurso.
Como a maioria das funções de aprendizado de máquina exige uma representação numérica de recursos, provavelmente será necessário realizar algumas transformações de dados para construir um vetor de recursos. Por exemplo, um recurso que especifica a localização da garagem pode incluir rótulos como “anexo à casa” ou “embutido”, que devem ser mapeados para valores numéricos. Quando você executa a previsão do preço da casa, a função de aprendizado de máquina será aplicada com esse vetor de recursos de entrada, bem como com os parâmetros internos do modelo treinado. A saída da função é o preço estimado da casa. Esta saída é chamada de rótulo.
Treinando o modelo
Os parâmetros internos do modelo que podem ser aprendidos (θ) são a parte do modelo que é aprendida com os dados de treinamento. Os parâmetros que podem ser aprendidos serão definidos durante o processo de treinamento. Um modelo de aprendizado de máquina supervisionado como o mostrado abaixo deve ser treinado para fazer previsões úteis.
Normalmente, o processo de treinamento começa com um modelo não treinado, onde todos os parâmetros que podem ser aprendidos são definidos com um valor inicial como zero. O modelo consome dados sobre várias características das casas, juntamente com os preços reais das casas. Gradualmente, identifica correlações entre as características das casas e os preços das casas, bem como o peso destas relações. O modelo ajusta seus parâmetros internos que podem ser aprendidos e os utiliza para fazer previsões.
Após o processo de treinamento, o modelo será capaz de estimar o preço de venda de uma casa avaliando suas características.
Algoritmos de aprendizado de máquina em código Java
O HousePriceModel
fornece dois métodos. Um deles implementa o algoritmo de aprendizagem para treinar (ou ajustar) o modelo. O outro método é usado para previsões.
O método fit()
O fit()
método é usado para treinar o modelo. Ele consome as características da casa, bem como os preços de venda da casa como parâmetros de entrada, mas não retorna nada. Este método requer a “resposta” correta para poder ajustar os parâmetros internos do modelo. Usando listagens de imóveis combinadas com preços de venda, o algoritmo de aprendizagem procura padrões nos dados de treinamento. A partir destes, produz parâmetros de modelo que generalizam a partir desses padrões. À medida que os dados de entrada se tornam mais precisos, os parâmetros internos do modelo são ajustados.
Listagem 1. O método fit() é usado para treinar um modelo de aprendizado de máquina
// load training data
// ...
// e.g. ({MSSubClass=60.0, LotFrontage=65.0, ...}, {MSSubClass=20.0, ...})
List<Map<String, Double>> houses = ...;
// e.g. (208500.0, 181500.0, 223500.0, 140000.0, 250000.0, ...)
List<Double> prices = ...;
// create and train the model
var model = new HousePriceModel();
model.fit(houses, prices);
Observe que os recursos da casa são digitados duas vezes no código. Isso ocorre porque o algoritmo de aprendizado de máquina usado para implementar o método fit() requer números como entrada. Todas as características da casa devem ser representadas numericamente para que possam ser usadas como x parâmetros na fórmula de regressão linear, conforme mostrado aqui:
hθ(x) = θ0 * x0 + ... + θn * xn
O modelo treinado de previsão de preços de casas pode ser parecido com o que você vê abaixo:
price = -490130.8527 * 1 + -241.0244 * MSSubClass + -143.716 * LotFrontage + … * …
Aqui, a casa de entrada apresenta recursos como MSSubClass
ou LotFrontage
são representados como x variáveis. Os parâmetros do modelo que podem ser aprendidos (θ) são definidos com valores como -490130,8527 ou -241,0244, que foram obtidos durante o processo de treinamento.
Este exemplo usa um algoritmo simples de aprendizado de máquina, que requer apenas alguns parâmetros do modelo. Um algoritmo mais complexo, como o de uma rede neural profunda, poderia exigir milhões de parâmetros de modelo; essa é uma das principais razões pelas quais o processo de treinamento de tais algoritmos requer alto poder computacional.
O método prever()
Depois de terminar de treinar o modelo, você pode usar o predict()
método para determinar o preço estimado de venda de uma casa. Este método consome dados sobre as características da casa e produz um preço de venda estimado. Na prática, um corretor de uma imobiliária poderia inserir características como o tamanho do lote (lot-area
), o número de quartos ou a qualidade geral da casa para receber um preço de venda estimado para uma determinada casa.
Transformando valores não numéricos
Freqüentemente, você se deparará com conjuntos de dados que contêm valores não numéricos. Por exemplo, o conjunto de dados Ames Housing usado para a competição Kaggle House Prices inclui listagens numéricas e textuais de características de casas:
Para complicar ainda mais as coisas, o conjunto de dados Kaggle também inclui valores vazios (marcados como NA), que não podem ser processados pelo algoritmo de regressão linear mostrado na Listagem 1.
Os registros de dados do mundo real são frequentemente incompletos, inconsistentes, carecem de comportamentos ou tendências desejados e podem conter erros. Isso normalmente ocorre nos casos em que os dados de entrada foram unidos usando fontes diferentes. Os dados de entrada devem ser convertidos em um conjunto de dados limpo antes de serem inseridos em um modelo.
Para melhorar os dados, você precisaria substituir o valor numérico (NA) ausente LotFrontage
valor. Você também precisaria substituir valores textuais, como MSZoning
“RL” ou “RM” com valores numéricos. Essas transformações são necessárias para converter os dados brutos em um formato sintaticamente correto que possa ser processado pelo seu modelo.
Depois de converter seus dados para um formato geralmente legível, talvez você ainda precise fazer alterações adicionais para melhorar a qualidade dos dados de entrada. Por exemplo, você pode remover valores que não seguem a tendência geral dos dados ou colocar categorias que ocorrem com pouca frequência em uma única categoria abrangente.
Aprendizado de máquina baseado em Java com Weka
Como você viu, desenvolver e testar uma função alvo requer parâmetros de configuração bem ajustados, como a taxa de aprendizagem adequada ou a contagem de iterações. O código de exemplo que você viu até agora reflete um conjunto muito pequeno de possíveis parâmetros de configuração, e os exemplos foram simplificados para manter o código legível. Na prática, você provavelmente dependerá de estruturas, bibliotecas e ferramentas de aprendizado de máquina.
A maioria das estruturas ou bibliotecas implementa uma extensa coleção de algoritmos de aprendizado de máquina. Além disso, eles fornecem APIs convenientes de alto nível para treinar, validar e processar modelos de dados. Weka é uma das estruturas mais populares para JVM.
Weka fornece uma biblioteca Java para uso programático, bem como um ambiente de trabalho gráfico para treinar e validar modelos de dados. No código abaixo, a biblioteca Weka é usada para criar um conjunto de dados de treinamento, que inclui recursos e um rótulo. O setClassIndex()
método é usado para marcar a coluna do rótulo. No Weka, o rótulo é definido como uma classe:
// define the feature and label attributes
ArrayList<Attribute> attributes = new ArrayList<>();
Attribute sizeAttribute = new Attribute("sizeFeature");
attributes.add(sizeAttribute);
Attribute squaredSizeAttribute = new Attribute("squaredSizeFeature");
attributes.add(squaredSizeAttribute);
Attribute priceAttribute = new Attribute("priceLabel");
attributes.add(priceAttribute);
// create and fill the features list with 5000 examples
Instances trainingDataset = new Instances("trainData", attributes, 5000);
trainingDataset.setClassIndex(trainingSet.numAttributes() - 1);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 90.0);
instance.setValue(squaredSizeAttribute, Math.pow(90.0, 2));
instance.setValue(priceAttribute, 249.0);
trainingDataset.add(instance);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 101.0);
...
O conjunto de dados ou Instance
O objeto também pode ser armazenado e carregado como um arquivo. Weka usa um ARFF (Attribute Relation File Format), que é suportado pelo ambiente de trabalho gráfico Weka. Este conjunto de dados é usado para treinar a função alvo, conhecida como classificador em Weka.
Lembre-se de que para treinar uma função alvo, você deve primeiro escolher o algoritmo de aprendizado de máquina. O código abaixo cria uma instância do LinearRegression
classificador. Este classificador é treinado chamando o método buildClassifier()
método. O buildClassifier()
O método ajusta os parâmetros teta com base nos dados de treinamento para encontrar o modelo mais adequado. Usando o Weka, você não precisa se preocupar em definir uma taxa de aprendizado ou contagem de iterações. Weka também faz o dimensionamento de recursos internamente:
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
Uma vez estabelecida, a função alvo pode ser usada para prever o preço de uma casa, conforme mostrado aqui:
Instances unlabeledInstances = new Instances("predictionset", attributes, 1);
unlabeledInstances.setClassIndex(trainingSet.numAttributes() - 1);
Instance unlabeled = new DenseInstance(3);
unlabeled.setValue(sizeAttribute, 1330.0);
unlabeled.setValue(squaredSizeAttribute, Math.pow(1330.0, 2));
unlabeledInstances.add(unlabeled);
double prediction = targetFunction.classifyInstance(unlabeledInstances.get(0));
Weka fornece um Evaluation
class para validar o classificador ou modelo treinado. No código abaixo, um conjunto de dados de validação dedicado é usado para evitar resultados tendenciosos. Medidas como custo ou taxa de erro serão impressas no console. Normalmente, os resultados da avaliação são usados para comparar modelos que foram treinados usando diferentes algoritmos de aprendizado de máquina ou uma variante destes:
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
Os exemplos acima usam regressão linear, que prevê um produto com valor numérico, como o preço de uma casa, com base nos valores dos insumos. A regressão linear oferece suporte à previsão de valores numéricos contínuos. Para prever valores binários sim/não ou classificadores, você pode usar um algoritmo de aprendizado de máquina, como árvore de decisão, rede neural ou regressão logística:
// using logistic regression
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);