Nano-neurônio - 7 funções JavaScript simples, mostrando como a máquina pode "aprender"

Um nano-neurônio é uma versão simplificada de um neurônio a partir do conceito de rede neural. O nano-neurônio executa a tarefa mais simples e é treinado para converter a temperatura de graus Celsius em graus Fahrenheit.


O código NanoNeuron.js consiste em 7 funções JavaScript simples que envolvem aprendizado, treinamento, previsão e propagação direta e reversa do sinal do modelo. O objetivo de escrever essas funções era fornecer ao leitor uma explicação básica e mínima (intuição) de como, afinal, uma máquina pode "aprender". O código não usa bibliotecas de terceiros. Como diz o ditado, apenas funções JavaScript "baunilha" simples.


Essas funções não são de forma alguma um guia completo para o aprendizado de máquina. Muitos conceitos de aprendizado de máquina estão ausentes ou simplificados! Essa simplificação é permitida com o único objetivo - fornecer ao leitor a compreensão e intuição mais básicas sobre como uma máquina pode "aprender" em princípio, de modo que, como resultado, "MAGIC of machine learning" pareça cada vez mais para o leitor como "MATEMÁTICA DO aprendizado de máquina".


Nanoneuron


O que nosso nano-neurônio “aprenderá”


Você pode ter ouvido falar de neurônios no contexto de redes neurais . Um nano-neurônio é uma versão simplificada desse mesmo neurônio. Neste exemplo, escreveremos sua implementação do zero. Por simplicidade, não construiremos uma rede de nano-neurônios. Vamos nos concentrar na criação de um único nano-neurônio e tentar ensiná-lo a converter a temperatura de graus Celsius em graus Fahrenheit. Em outras palavras, vamos ensiná-lo a prever a temperatura em graus Fahrenheit com base na temperatura em graus Celsius.


A propósito, a fórmula para converter graus Celsius em graus Fahrenheit é a seguinte:


Celsius em Fahrenheit


Mas, no momento, nosso nano-neurônio não sabe nada sobre essa fórmula ...


Modelo nano-neurônio


Vamos começar criando uma função que descreve o modelo do nosso nano neurônio. Este modelo é uma relação linear simples entre x e y , com a seguinte aparência: y = w * x + b . Simplificando, nosso nano-neurônio é uma criança que pode desenhar uma linha reta no sistema de coordenadas XY .


As variáveis w e b são parâmetros do modelo. Um nano-neurônio conhece apenas esses dois parâmetros de uma função linear. Esses parâmetros são precisamente o que nosso nano-neurônio aprenderá durante o processo de treinamento.


A única coisa que um nano-neurônio pode fazer nesta fase é simular relações lineares. Ele faz isso no método predict() , que pega uma variável x na entrada e prediz a variável y na saída. Nenhuma mágica.


 function NanoNeuron(w, b) { this.w = w; this.b = b; this.predict = (x) => { return x * this.w + this.b; } } 

_ (... espere ... regressão linear é você, ou o quê?) _


Converter graus Celsius em graus Fahrenheit


A temperatura em graus Celsius pode ser convertida em graus Fahrenheit de acordo com a fórmula: f = 1.8 * c + 32 , onde c é a temperatura em graus Celsius f é a temperatura em graus Fahrenheit.


 function celsiusToFahrenheit(c) { const w = 1.8; const b = 32; const f = c * w + b; return f; }; 

Como resultado, queremos que nosso nano-neurônio seja capaz de simular essa função específica. Ele terá que adivinhar (aprender) que o parâmetro w = 1.8 b = 32 sem conhecê-lo com antecedência.


É assim que a função de conversão aparece no gráfico. É isso que nosso "bebê" nano-neural deve aprender a "desenhar":


Conversão de Celsius para Fahrenheit


Geração de dados


Na programação clássica, conhecemos os dados de entrada ( x ) e o algoritmo para converter esses dados (parâmetros w ), mas os dados de saída ( y ) são desconhecidos. A saída é calculada com base na entrada usando um algoritmo conhecido. No aprendizado de máquina, pelo contrário, apenas os dados de entrada e saída ( x e y ) são conhecidos, mas o algoritmo para alternar de x para y desconhecido (parâmetros w e b ).


É a geração de entrada e saída que vamos fazer agora. Precisamos gerar dados para treinar nosso modelo e dados para testar o modelo. A função auxiliar celsiusToFahrenheit() nos ajudará com isso. Cada um dos conjuntos de dados de treinamento e teste é um conjunto de pares x e y . Por exemplo, se x = 2 , y = 35,6 e assim por diante.


No mundo real, a maioria dos dados provavelmente será coletada , não gerada . Por exemplo, esses dados coletados podem ser um conjunto de pares de "fotos de rosto" -> "nome da pessoa".

Usaremos o conjunto de dados TRAINING para treinar nosso nano-neurônio. Antes que ele cresça e seja capaz de tomar decisões por conta própria, devemos ensinar a ele o que é "verdadeiro" e o que é "falso" usando dados "corretos" de um conjunto de treinamento.


A propósito, aqui o princípio da vida “lixo na entrada - lixo na saída” é claramente traçado. Se um nano-neurônio joga uma "mentira" no kit de treinamento que 5 ° C é convertido em 1000 ° F, depois de muitas iterações de treinamento, ele acreditará nisso e converterá corretamente todos os valores de temperatura, exceto 5 ° C. Precisamos ter muito cuidado com os dados de treinamento que carregamos todos os dias em nossa rede neural cerebral.

Distraído. Vamos continuar.


Usaremos o conjunto de dados TEST para avaliar quão bem nosso nano neurônio foi treinado e podemos fazer previsões corretas sobre novos dados que ele não viu durante o treinamento.


 function generateDataSets() { // xTrain -> [0, 1, 2, ...], // yTrain -> [32, 33.8, 35.6, ...] const xTrain = []; const yTrain = []; for (let x = 0; x < 100; x += 1) { const y = celsiusToFahrenheit(x); xTrain.push(x); yTrain.push(y); } // xTest -> [0.5, 1.5, 2.5, ...] // yTest -> [32.9, 34.7, 36.5, ...] const xTest = []; const yTest = []; //   0.5    1,       //   ,       . for (let x = 0.5; x < 100; x += 1) { const y = celsiusToFahrenheit(x); xTest.push(x); yTest.push(y); } return [xTrain, yTrain, xTest, yTest]; } 

Estimativa de erro de previsão


Precisamos de uma certa métrica (medida, número, classificação) que mostre quão próxima é a previsão de um nano neurônio. Em outras palavras, esse número / métrica / função deve mostrar quão certo ou errado o nano neurônio está. É como na escola, um aluno pode obter uma nota de 5 ou 2 por seu controle.


No caso de um nano-neurônio, seu erro (erro) entre o valor verdadeiro de y e o valor previsto de prediction será produzido pela fórmula:


Custo de previsão


Como pode ser visto na fórmula, consideraremos o erro como uma simples diferença entre os dois valores. Quanto mais próximos os valores estiverem, menor a diferença. Usamos o quadrado aqui para se livrar do sinal, para que no final (1 - 2) ^ 2 equivalente a (2 - 1) ^ 2 . A divisão por 2 ocorre apenas para simplificar o significado da derivada dessa função na fórmula para propagação reversa de um sinal (mais sobre isso abaixo).


A função de erro, neste caso, terá a seguinte aparência:


 function predictionCost(y, prediction) { return (y - prediction) ** 2 / 2; // ie -> 235.6 } 

Propagação direta de sinal


A propagação direta do sinal através do nosso modelo significa fazer previsões para todos os pares do conjunto de dados de treinamento yTrain e yTrain e calcular o erro médio (erro) dessas previsões.


Nós apenas deixamos nosso nano neurônio "falar", permitindo que ele fizesse previsões (convertesse a temperatura). Ao mesmo tempo, um nano-neurônio nesta fase pode estar muito errado. O valor médio do erro de previsão nos mostrará até que ponto nosso modelo está / está próximo da verdade no momento. O valor do erro é muito importante aqui, pois, alterando os parâmetros w b propagação direta do sinal, podemos avaliar se nosso nano neurônio se tornou "mais inteligente" com novos parâmetros ou não.


O erro médio de previsão de um nano neurônio será realizado usando a seguinte fórmula:


Custo médio


Onde m é o número de cópias de treinamento (no nosso caso, temos 100 pares de dados).


Veja como podemos implementar isso no código:


 function forwardPropagation(model, xTrain, yTrain) { const m = xTrain.length; const predictions = []; let cost = 0; for (let i = 0; i < m; i += 1) { const prediction = nanoNeuron.predict(xTrain[i]); cost += predictionCost(yTrain[i], prediction); predictions.push(prediction); } //     . cost /= m; return [predictions, cost]; } 

Propagação Reversa de Sinais


Agora que sabemos como nosso nano-neurônio está certo ou errado em suas previsões (com base no valor médio do erro), como podemos tornar as previsões mais precisas?


A propagação reversa do sinal nos ajudará com isso. A propagação do sinal de retorno é o processo de avaliar o erro de um nano-neurônio e, em seguida, ajustar seus parâmetros w para que as próximas previsões do nano-neurônio para todo o conjunto de dados de treinamento se tornem um pouco mais precisas.


É aqui que o aprendizado de máquina se torna como mágica. O conceito-chave aqui é uma derivada da função , que mostra qual etapa de tamanho e qual caminho precisamos seguir para abordar o mínimo da função (no nosso caso, o mínimo da função de erro).


O objetivo final do treinamento de um nano-neurônio é encontrar o mínimo da função de erro (veja a função acima). Se pudermos encontrar esses valores de w e b nos quais o valor médio da função de erro é pequeno, isso significa que nosso nano neurônio lida bem com as previsões de temperatura em graus Fahrenheit.


Derivativos são um tópico amplo e separado que não abordaremos neste artigo. MathIsFun é um ótimo recurso que pode fornecer uma compreensão básica de derivadas.


Uma coisa que precisamos aprender com a essência da derivada e que nos ajudará a entender como a retropropagação do sinal funciona é que a derivada de uma função em um ponto específico x e y , por definição, é uma linha tangente à curva dessa função em x e y indica a direção para o mínimo da função .


Inclinação derivada


Imagem tirada do MathIsFun


Por exemplo, no gráfico acima, você vê que, no ponto (x=2, y=4) inclinação da tangente nos mostra que precisamos nos mover para a e para para obter o mínimo da função. Observe também que quanto maior a inclinação da tangente, mais rápido devemos mover para o ponto mínimo.


As derivadas de nossa função de erro médio averageCost com averageCost aos parâmetros w e b terão a seguinte aparência:


dW


dB


Onde m é o número de cópias de treinamento (no nosso caso, temos 100 pares de dados).


Você pode ler mais detalhadamente sobre como obter a derivada de funções complexas aqui .


 function backwardPropagation(predictions, xTrain, yTrain) { const m = xTrain.length; //           'w'  'b'. //      0. let dW = 0; let dB = 0; for (let i = 0; i < m; i += 1) { dW += (yTrain[i] - predictions[i]) * xTrain[i]; dB += yTrain[i] - predictions[i]; } //    . dW /= m; dB /= m; return [dW, dB]; } 

Modelo de treinamento


Agora sabemos como estimar o erro / erro das previsões do nosso modelo de nano-neurônios para todos os dados de treinamento (propagação direta do sinal). Também sabemos como ajustar os parâmetros w e b modelo de nano-neurônios (propagação reversa do sinal) para melhorar a precisão das previsões. O problema é que, se realizarmos a propagação para frente e para trás do sinal apenas uma vez, isso não será suficiente para o nosso modelo identificar e aprender as dependências e leis nos dados de treinamento. Você pode comparar isso com a visita escolar de um dia de um aluno. Ele / ela deve frequentar a escola regularmente, dia após dia, ano após ano, para aprender todo o material.


Portanto, devemos repetir a propagação para frente e para trás do sinal muitas vezes. É trainModel() função trainModel() . Ela é como uma "professora" para o modelo do nosso nano neurônio:


  • ela passará algum tempo ( epochs ) com nosso nano neurônio ainda bobo, tentando treiná-lo,
  • ela usará livros especiais (conjuntos de dados yTrain e yTrain ) para treinamento,
  • incentiva nosso "aluno" a estudar com mais diligência (mais rápido) usando o parâmetro alpha , que controla essencialmente a velocidade do aprendizado.

Algumas palavras sobre o parâmetro alpha . Este é apenas um coeficiente (multiplicador) para os valores das variáveis dW e dB , que calculamos durante a propagação posterior do sinal. Portanto, a derivada nos mostrou a direção para o mínimo da função de erro (os sinais dos valores de dW e dB nos dizem isso). A derivada também nos mostrou a rapidez com que precisamos avançar para o mínimo da função (os valores absolutos de dW e dB nos dizem isso). Agora, precisamos multiplicar o tamanho da etapa por alpha , a fim de ajustar a velocidade de nossa abordagem a um mínimo (o tamanho total da etapa). Às vezes, se usarmos valores grandes para alpha , podemos executar etapas tão grandes que podemos simplesmente ultrapassar o mínimo da função, ignorando-a.


Por analogia com o “professor”, quanto mais forte ela forçaria nosso “nano-aluno” a aprender, mais rápido ele aprenderia, MAS, se você forçar e pressioná-lo muito, então nosso “nano-aluno” poderá sofrer um colapso nervoso e apatia completa e ele não aprenderá nada.


Atualizaremos os parâmetros do nosso modelo w e b seguinte maneira:


w


b


E é assim que o treinamento em si se parece:


 function trainModel({model, epochs, alpha, xTrain, yTrain}) { //     -.  . const costHistory = []; //    ()  for (let epoch = 0; epoch < epochs; epoch += 1) { //   . const [predictions, cost] = forwardPropagation(model, xTrain, yTrain); costHistory.push(cost); //   . const [dW, dB] = backwardPropagation(predictions, xTrain, yTrain); //    -,    . nanoNeuron.w += alpha * dW; nanoNeuron.b += alpha * dB; } return costHistory; } 

Juntando todos os recursos


Hora de usar todas as funções criadas anteriormente juntas.


Crie uma instância do modelo nano-neurônio. No momento, o nano-neurônio não sabe nada sobre quais devem ser os parâmetros w e b . Então, vamos definir w e b aleatoriamente.


 const w = Math.random(); // ie -> 0.9492 const b = Math.random(); // ie -> 0.4570 const nanoNeuron = new NanoNeuron(w, b); 

Geramos conjuntos de dados de treinamento e teste.


 const [xTrain, yTrain, xTest, yTest] = generateDataSets(); 

Agora vamos tentar treinar nosso modelo usando pequenos passos ( 0.0005 ) para 70000 épocas. Você pode experimentar com esses parâmetros, eles são determinados empiricamente.


 const epochs = 70000; const alpha = 0.0005; const trainingCostHistory = trainModel({model: nanoNeuron, epochs, alpha, xTrain, yTrain}); 

Vamos verificar como o valor do erro do nosso modelo mudou durante o treinamento. Esperamos que o valor do erro após o treinamento seja significativamente menor do que antes do treinamento. Isso significa que o nosso nano-neurônio é mais sábio. A opção oposta também é possível quando, após o treinamento, o erro de previsão apenas aumenta (por exemplo, grandes valores da etapa de aprendizado alpha ).


 console.log('  :', trainingCostHistory[0]); // ie -> 4694.3335043 console.log('  :', trainingCostHistory[epochs - 1]); // ie -> 0.0000024 

E aqui está como o valor do erro do modelo mudou durante o treinamento. No eixo x são épocas (em milhares). Esperamos que o gráfico esteja diminuindo.


Processo de treinamento


Vejamos quais parâmetros nosso nano-neurônio “aprendeu”. Esperamos que os parâmetros w e b sejam semelhantes aos parâmetros com o mesmo nome da função celsiusToFahrenheit() ( w = 1.8 b = 32 ), porque foi o nano-neurônio dela que tentei simular.


 console.log(' -:', {w: nanoNeuron.w, b: nanoNeuron.b}); // ie -> {w: 1.8, b: 31.99} 

Como você pode ver, o nano-neurônio está muito próximo da função celsiusToFahrenheit() .


Agora vamos ver quão precisas são as previsões de nosso nano-neurônio para dados de teste que ele não viu durante o treinamento. O erro de previsão para os dados de teste deve estar próximo do erro de previsão para os dados de treinamento. Isso significa que o nano-neurônio aprendeu as dependências corretas e pode abstrair corretamente sua experiência a partir de dados anteriormente desconhecidos (esse é todo o valor do modelo).


 [testPredictions, testCost] = forwardPropagation(nanoNeuron, xTest, yTest); console.log('   :', testCost); // ie -> 0.0000023 

Agora, como nosso "nano-bebê" foi bem treinado na "escola" e agora sabe como converter com precisão graus Celsius em graus Fahrenheit, mesmo para dados que ele não viu, podemos chamá-lo de "inteligente". Agora podemos até pedir conselhos a ele sobre conversão de temperatura, e esse foi o objetivo de todo o treinamento.


 const tempInCelsius = 70; const customPrediction = nanoNeuron.predict(tempInCelsius); console.log(`- "",  ${tempInCelsius}°C   :`, customPrediction); // -> 158.0002 console.log('  :', celsiusToFahrenheit(tempInCelsius)); // -> 158 

Muito perto! Como as pessoas, nosso nano-neurônio é bom, mas não é perfeito :)


Codificação bem sucedida!


Como executar e testar um nano-neurônio


Você pode clonar o repositório e executar o nano neurônio localmente:


 git clone https://github.com/trekhleb/nano-neuron.git cd nano-neuron 

 node ./NanoNeuron.js 

Conceitos perdidos


Os seguintes conceitos de aprendizado de máquina foram omitidos ou simplificados para facilitar a explicação.


Separação de conjuntos de dados de treinamento e teste


Geralmente você tem um grande conjunto de dados. Dependendo do número de cópias deste conjunto, sua divisão em conjuntos de treinamento e teste pode ser realizada na proporção de 70/30. Os dados no conjunto devem ser misturados aleatoriamente antes de serem divididos. Se a quantidade de dados for grande (por exemplo, milhões), a divisão em conjuntos de teste e treinamento poderá ser realizada em proporções próximas a 90/10 ou 95/5.


Poder online


Normalmente, você não encontrará casos em que apenas um neurônio seja usado. A força está na rede de tais neurônios. Uma rede neural pode aprender dependências muito mais complexas.


Também no exemplo acima, nosso nano-neurônio pode parecer mais uma regressão linear simples do que uma rede neural.


Normalização de entrada


Antes do treinamento, é habitual normalizar os dados de entrada .


Implementação vetorial


Para redes neurais, os cálculos de vetor (matriz) são muito mais rápidos que os cálculos for loops. Geralmente, a propagação direta e reversa do sinal é realizada usando operações de matriz, por exemplo, a biblioteca Python Numpy .


Função de erro mínimo


A função de erro que usamos para o nano neurônio é muito simplificada. Ele deve conter componentes logarítmicos . Uma mudança na fórmula para a função de erro também implicará uma mudança nas fórmulas para a propagação para frente e para trás do sinal.


Função de ativação


Normalmente, o valor de saída do neurônio passa pela função de ativação. Para a ativação, funções como Sigmoid , ReLU e outras podem ser usadas.

Source: https://habr.com/ru/post/pt479220/


All Articles