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".

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:

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":

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() {
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:

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;
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:

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); }
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 .

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:


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;
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:


E é assim que o treinamento em si se parece:
function trainModel({model, epochs, alpha, xTrain, yTrain}) {
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();
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]);
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.

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});
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);
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);
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.