JavaRush /Blogue Java /Random-PT /Aprendizado de máquina para desenvolvedores Java, parte 2...

Aprendizado de máquina para desenvolvedores Java, parte 2

Publicado no grupo Random-PT
Aprendizado de máquina para desenvolvedores Java, parte 1
Aprendizado de máquina para desenvolvedores Java, parte 2 a 1

Estimativa de Função Objetivo

Lembremos que a função alvo , também conhecida como função de predição, é o resultado do processo de preparação ou treinamento. Matematicamente, o desafio é encontrar uma função que receba uma variável como entrada хe retorne o valor previsto у.
Aprendizado de máquina para desenvolvedores Java, parte 2 a 2
No aprendizado de máquina, uma função de custo (J(θ))é usada para calcular o valor do erro ou “custo” de uma determinada função objetivo.
Aprendizado de máquina para desenvolvedores Java, parte 2 a 3
A função de custo mostra quão bem o modelo se ajusta aos dados de treinamento. Para determinar o custo da função objetivo mostrada acima, é necessário calcular o erro quadrático de cada casa exemplo (i). O erro é a distância entre o valor calculado уe o valor real yda casa do exemplo i.
Aprendizado de máquina para desenvolvedores Java, parte 2 a 4
Por exemplo, o preço real de uma casa com área de 1330 = 6.500.000€ . E a diferença entre o preço da casa previsto pela função objetivo treinada é de € 7.032.478 : a diferença (ou erro) é de € 532.478 . Você também pode ver essa diferença no gráfico acima. A diferença (ou erro) é mostrada como linhas verticais tracejadas vermelhas para cada par de treinamento de área de preço. Tendo calculado o custo da função objetivo treinada, você precisa somar o erro quadrático de cada casa no exemplo e calcular o valor principal. Quanto menor o valor do preço (J(θ)), mais precisas serão as previsões da nossa função objetivo. A Listagem 3 mostra uma implementação Java simples de uma função de custo que recebe como entrada uma função objetivo, uma lista de dados de treinamento e rótulos associados a eles. Os valores de previsão serão calculados em loop e o erro será calculado subtraindo o valor real do preço (retirado do rótulo). Posteriormente, o quadrado dos erros será somado e o valor do erro será calculado. O custo será retornado como um valor do tipo double:

Listagem-3

public static double cost(Function<ltDouble[], Double> targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels) {
 int m = dataset.size();
 double sumSquaredErrors = 0;

 // рассчет квадрата ошибки («разницы») для каждого тренировочного примера и //добавление его к сумме
 for (int i = 0; i < m; i++) {
 // получаем вектор признаков из текущего примера
 Double[] featureVector = dataset.get(i);
 // предсказываем meaning и вычисляем ошибку базируясь на реальном
 //значении (метка)
 double predicted = targetFunction.apply(featureVector);
 double label = labels.get(i);
 double gap = predicted - label;
 sumSquaredErrors += Math.pow(gap, 2);
 }

 // Вычисляем и возращаем meaning ошибки (чем меньше тем лучше)
 return (1.0 / (2 * m)) * sumSquaredErrors;
}
Interessado em ler sobre Java? Junte-se ao grupo de desenvolvedores Java !

Aprendendo a função alvo

Embora a função de custo ajude a avaliar a qualidade da função objetivo e dos parâmetros teta, você ainda precisa encontrar os parâmetros teta mais adequados. Você pode usar o algoritmo de descida gradiente para isso.

Gradiente descendente

A descida gradiente minimiza a função de custo. Isso significa que é utilizado para encontrar os parâmetros teta que possuem o custo mínimo (J(θ))com base nos dados de treinamento. Aqui está um algoritmo simplificado para calcular valores teta novos e mais apropriados:
Aprendizado de máquina para desenvolvedores Java, Parte 2 a 5
Portanto, os parâmetros do vetor teta irão melhorar a cada iteração do algoritmo. O coeficiente de aprendizagem α especifica o número de cálculos em cada iteração. Esses cálculos podem ser realizados até que valores teta “bons” sejam encontrados. Por exemplo, a função de regressão linear abaixo possui três parâmetros teta:
Aprendizado de máquina para desenvolvedores Java, parte 2 a 6
A cada iteração, um novo valor será calculado para cada um dos parâmetros teta: ,, e . Após cada iteração, uma implementação nova e mais apropriada pode ser criada usando o novo vetor teta 0 , θ 1 , θ 2 } . A Listagem -4 mostra o código Java para o algoritmo de decaimento de gradiente. Theta para a função de regressão será treinado usando dados de treinamento, dados de marcadores e taxa de aprendizagem . O resultado será uma função objetivo melhorada usando parâmetros teta. O método será chamado repetidamente, passando a nova função objetivo e os novos parâmetros teta dos cálculos anteriores. E essas chamadas serão repetidas até que a função objetivo configurada atinja um patamar mínimo: θ0θ1θ2LinearRegressionFunction(α)train()

Listagem-4

public static LinearRegressionFunction train(LinearRegressionFunction targetFunction,
 List<ltDouble[]> dataset,
 List<ltDouble> labels,
 double alpha) {
 int m = dataset.size();
 double[] thetaVector = targetFunction.getThetas();
 double[] newThetaVector = new double[thetaVector.length];

 // вычисление нового значения тета для каждого element тета массива
 for (int j = 0; j < thetaVector.length; j++) {
 // сумируем разницу ошибки * признак
 double sumErrors = 0;
 for (int i = 0; i < m; i++) {
 Double[] featureVector = dataset.get(i);
 double error = targetFunction.apply(featureVector) - labels.get(i);
 sumErrors += error * featureVector[j];
 }

 //вычисляем новые значения тета
 double gradient = (1.0 / m) * sumErrors;
 newThetaVector[j] = thetaVector[j] - alpha * gradient;
 }

 return new LinearRegressionFunction(newThetaVector);
}
Para garantir que o custo diminua continuamente, você pode executar a função de custo J(θ)após cada etapa de treinamento. Após cada iteração, o custo deve diminuir. Se isso não acontecer, significa que o valor do coeficiente de aprendizagem é muito grande e o algoritmo simplesmente não atingiu o valor mínimo. Nesse caso, o algoritmo de decaimento do gradiente falha. Os gráficos abaixo mostram a função objetivo usando os novos parâmetros teta calculados, começando com o vetor teta inicial {1.0, 1.0}. A coluna da esquerda mostra o gráfico da função de predição após 50 iterações; coluna do meio após 200 repetições; e a coluna direita após 1000 repetições. A partir disso, podemos ver que o preço diminui após cada iteração e a nova função objetivo se ajusta cada vez melhor. Após 500-600 repetições, os parâmetros teta não mudam mais significativamente e o preço atinge um patamar estável. Depois disso, a precisão da função alvo não pode ser melhorada desta forma.
Aprendizado de máquina para desenvolvedores Java, Parte 2 a 7
Neste caso, mesmo que o custo já não diminua significativamente após 500-600 iterações, a função objectivo ainda não é óptima. Isto indica uma discrepância . No aprendizado de máquina, o termo “inconsistência” é usado para indicar que o algoritmo de aprendizado não encontra tendências subjacentes nos dados. Com base na experiência da vida real, é provável que se espere uma redução no preço por metro quadrado para propriedades maiores. A partir disso podemos concluir que o modelo usado para o processo de aprendizagem da função alvo não se ajusta suficientemente aos dados. A discrepância geralmente se deve à simplificação excessiva do modelo. Isso aconteceu no nosso caso, a função objetivo é muito simples e para análise utiliza um único parâmetro - a área da casa. Mas esta informação não é suficiente para prever com precisão o preço de uma casa.

Adicionando recursos e dimensionando-os

Se você achar que sua função objetivo não corresponde ao problema que você está tentando resolver, ela precisará ser ajustada. Uma maneira comum de corrigir inconsistências é adicionar recursos adicionais ao vetor de recursos. No exemplo do preço de uma casa, pode adicionar características como o número de quartos ou a idade da casa. Ou seja, em vez de usar um vetor com um valor de recurso {size}para descrever uma casa, você pode usar um vetor com vários valores, por exemplo, {size, number-of-rooms, age}. Em alguns casos, o número de recursos nos dados de treinamento disponíveis não é suficiente. Então vale a pena tentar usar recursos polinomiais que são calculados usando os existentes. Por exemplo, você tem a oportunidade de estender a função objetivo para determinar o preço de uma casa para que inclua um recurso calculado de metros quadrados (x2):
Aprendizado de máquina para desenvolvedores Java, parte 2 a 8
O uso de vários recursos requer escalonamento de recursos , que é usado para padronizar o intervalo entre diferentes recursos. Assim, o intervalo de valores do atributo tamanho 2 é significativamente maior que o intervalo de valores do atributo tamanho. Sem escalonamento de recursos, o tamanho 2 influenciará indevidamente a função de custo. O erro introduzido pelo atributo tamanho 2 será significativamente maior que o erro introduzido pelo atributo tamanho. Um algoritmo simples de escalonamento de recursos é fornecido abaixo:
Aprendizado de máquina para desenvolvedores Java, parte 2 a 9
Este algoritmo é implementado na classe FeaturesScalingno código de exemplo abaixo. A aula FeaturesScalingapresenta um método comercial para criar uma função de escalonamento ajustada aos dados de treinamento. Internamente, as instâncias de dados de treinamento são utilizadas para calcular os valores médio, mínimo e máximo. A função resultante pega o vetor de recursos e produz um novo com os recursos dimensionados. O escalonamento de recursos é necessário tanto para o processo de aprendizagem quanto para o processo de previsão, conforme mostrado abaixo:
// создание массива данных
List<ltDouble[]> dataset = new ArrayList<>();
dataset.add(new Double[] { 1.0, 90.0, 8100.0 }); // feature vector of house#1
dataset.add(new Double[] { 1.0, 101.0, 10201.0 }); // feature vector of house#2
dataset.add(new Double[] { 1.0, 103.0, 10609.0 }); // ...
//...

// создание меток
List<ltDouble> labels = new ArrayList<>();
labels.add(249.0); // price label of house#1
labels.add(338.0); // price label of house#2
labels.add(304.0); // ...
//...

// создание расширенного списка признаков
Function<ltDouble[], Double[]> scalingFunc = FeaturesScaling.createFunction(dataset);
List<ltDouble[]> scaledDataset = dataset.stream().map(scalingFunc).collect(Collectors.toList());

// создаем функцию которая инициализирует теты и осуществляет обучение //используя коэффициент обучения 0.1

LinearRegressionFunction targetFunction = new LinearRegressionFunction(new double[] { 1.0, 1.0, 1.0 });
for (int i = 0; i < 10000; i++) {
 targetFunction = Learner.train(targetFunction, scaledDataset, labels, 0.1);
}

// делаем предсказание стоимости дома с площадью 600 m2
Double[] scaledFeatureVector = scalingFunc.apply(new Double[] { 1.0, 600.0, 360000.0 });
double predictedPrice = targetFunction.apply(scaledFeatureVector);
À medida que mais e mais recursos são adicionados, o ajuste à função objetivo aumenta, mas tome cuidado. Se você for longe demais e adicionar muitos recursos, poderá acabar aprendendo uma função objetivo que está superajustada.

Sobrecorrespondência e validação cruzada

O overfitting ocorre quando a função ou modelo objetivo se ajusta muito bem aos dados de treinamento, a ponto de capturar ruído ou variações aleatórias nos dados de treinamento. Um exemplo de overfitting é mostrado no gráfico mais à direita abaixo:
Aprendizado de máquina para desenvolvedores Java, parte 2 a 10
No entanto, um modelo de overfitting tem um desempenho muito bom em dados de treinamento, mas terá um desempenho ruim em dados reais desconhecidos. Existem várias maneiras de evitar o overfitting.
  • Use um conjunto de dados maior para treinamento.
  • Use menos recursos conforme mostrado nos gráficos acima.
  • Use um algoritmo de aprendizado de máquina aprimorado que leve em consideração a regularização.
Se um algoritmo de predição superajusta os dados de treinamento, é necessário eliminar recursos que não beneficiam sua precisão. A dificuldade é encontrar características que tenham um efeito mais significativo na precisão da previsão do que outras. Conforme mostrado nos gráficos, o overfit pode ser determinado visualmente por meio de gráficos. Isso funciona bem para gráficos com 2 ou 3 coordenadas, fica difícil traçar e avaliar o gráfico se você usar mais de 2 recursos. Na validação cruzada, você testa novamente os modelos após o treinamento usando dados desconhecidos para o algoritmo após a conclusão do processo de treinamento. Os dados rotulados disponíveis devem ser divididos em 3 conjuntos:
  • dados de treinamento;
  • dados de verificação;
  • dados de teste.
Neste caso, 60 por cento dos registros rotulados que caracterizam as casas devem ser utilizados no processo de treinamento de variantes do algoritmo alvo. Após o processo de treinamento, metade dos dados restantes (não utilizados anteriormente) devem ser usados ​​para verificar se o algoritmo alvo treinado tem um bom desempenho nos dados desconhecidos. Normalmente, o algoritmo que apresenta melhor desempenho do que outros é selecionado para uso. Os dados restantes são usados ​​para calcular o valor do erro para o modelo final selecionado. Existem outras técnicas de validação cruzada, como k-fold . No entanto, não os descreverei neste artigo.

Ferramentas de aprendizado de máquina e estrutura Weka

A maioria das estruturas e bibliotecas fornece uma extensa coleção de algoritmos de aprendizado de máquina. Além disso, eles fornecem uma interface conveniente de alto nível para treinamento, teste e processamento de modelos de dados. Weka é uma das estruturas mais populares para JVM. Weka é uma biblioteca Java prática que contém testes gráficos para validação de modelos. O exemplo abaixo usa a biblioteca Weka para criar um conjunto de dados de treinamento que contém recursos e rótulos. Método setClassIndex()- para marcação. No Weka, um rótulo é definido como uma classe:
// определяем атрибуты для признаков и меток
ArrayList<ltAttribute> 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);


// создаем и заполняем список признаков 5000 примеров
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 e o objeto de amostra podem ser salvos e carregados de um arquivo. Weka usa ARFF (Attribute Relation File Format), que é suportado pelos benchmarks gráficos do Weka. Este conjunto de dados é usado para treinar uma função objetivo conhecida como classificador no Weka. Primeiro de tudo, você deve definir a função objetivo. O código abaixo LinearRegressioncriará uma instância do classificador. Este classificador será treinado usando o buildClassifier(). O método buildClassifier()seleciona parâmetros teta com base em dados de treinamento em busca do melhor modelo alvo. Com o Weka, você não precisa se preocupar em definir a taxa de aprendizado ou o número de iterações. Weka também realiza escalonamento de recursos de forma independente.
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
Uma vez feitas essas configurações, a função objetivo pode ser usada para prever o preço de uma casa, conforme mostrado abaixo:
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 uma classe Evaluationpara testar um classificador ou modelo treinado. No código abaixo, uma matriz selecionada de dados de validação é usada para evitar resultados falsos. Os resultados da medição (custo do erro) serão exibidos 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 variações destes:
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
O exemplo acima utiliza regressão linear, que prevê valores numéricos, como o preço de uma casa, com base nos valores de entrada. A regressão linear suporta a previsão de valores numéricos contínuos. Para prever valores binários (“Sim” e “Não”), você precisa usar outros algoritmos de aprendizado de máquina. Por exemplo, árvore de decisão, redes neurais ou regressão logística.
// использование логистической регрессии
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);
Você pode usar um desses algoritmos, por exemplo, para prever se uma mensagem de e-mail é spam, ou prever o tempo, ou prever se uma casa venderá bem. Se você quiser ensinar seu algoritmo a prever o tempo ou a rapidez com que uma casa será vendida, você precisará de um conjunto de dados diferente, por exemplo.topseller:
// использование атрибута маркера topseller instead of атрибута маркера цена
ArrayList<string> classVal = new ArrayList<>();
classVal.add("true");
classVal.add("false");

Attribute topsellerAttribute = new Attribute("topsellerLabel", classVal);
attributes.add(topsellerAttribute);
Este conjunto de dados será usado para treinar um novo classificador topseller. Depois de treinada, a chamada de previsão deve retornar um índice de classe de token que pode ser usado para obter o valor previsto.
int idx = (int) targetFunction.classifyInstance(unlabeledInstances.get(0));
String prediction = classVal.get(idx);

Conclusão

Embora o aprendizado de máquina esteja intimamente relacionado à estatística e use muitos conceitos matemáticos, o kit de ferramentas de aprendizado de máquina permite que você comece a integrar o aprendizado de máquina em seus programas sem um conhecimento profundo de matemática. No entanto, quanto melhor você compreender os algoritmos de aprendizado de máquina subjacentes, como o algoritmo de regressão linear que exploramos neste artigo, mais será capaz de escolher o algoritmo certo e ajustá-lo para obter o desempenho ideal. Tradução do inglês. Autor: Gregor Roth, arquiteto de software, JavaWorld.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION