Pular para o conteúdo

Construindo redes neurais do zero

cover (9) | | Cheesecake Labs
Resumo
  • Redes neurais são algoritmos de machine learning inspirados no cérebro humano, formados por neurônios interconectados que aprendem a partir de grandes volumes de dados, com aplicações como reconhecimento de imagem, processamento de linguagem natural e análise preditiva.
  • Construir uma rede neural eficaz envolve quatro componentes: definir a tarefa, escolher o modelo, estabelecer métricas de desempenho (como acurácia, precisão, recall ou F1-score) e fornecer experiência por meio dos dados de treinamento.
  • O Perceptron, proposto por McCulloch e Pitts em 1943, é a primeira representação matemática do neurônio artificial e combina entradas ponderadas, bias e função de ativação, servindo de base para arquiteturas mais avançadas.
  • O guia aplica o neurônio artificial a um problema de regressão linear, usando o Mean Squared Error (MSE) para medir o erro e backpropagation com gradiente descendente para treinar o modelo, destacando a escolha empírica de hiperparâmetros como learning rate e epochs para evitar underfit e overfit.

Redes neurais são algoritmos de machine learning poderosos, que já transformaram inúmeros setores. Elas sustentam desde detecção de fraude e previsão de demanda até recomendações personalizadas e sistemas autônomos, e são um bom caminho para levar decisões mais inteligentes para dentro das suas aplicações. 

Neste guia, você vai percorrer os fundamentos das redes neurais, das primeiras representações do neurônio artificial até a implementação do seu próprio modelo de regressão linear. 

Prepare-se para explorar todo o potencial das redes neurais e começar sua jornada em inteligência artificial.

1. Redes neurais como algoritmo de machine learning

Redes neurais são um tipo de algoritmo de machine learning inspirado na estrutura e no funcionamento do cérebro humano. Elas são formadas por nós interconectados, os “neurônios”, que aprendem a executar tarefas específicas analisando grandes volumes de dados.

Redes neurais têm muitas aplicações: reconhecimento de imagem, processamento de linguagem natural, reconhecimento de fala e análise preditiva. Elas são muito boas em identificar padrões e tomar decisões complexas, o que as torna uma ferramenta valiosa em vários setores.

Redes neurais são bastante flexíveis e se adaptam a uma variedade de problemas. Elas aprendem com os dados e melhoram seu desempenho ao longo do tempo, o que as torna ferramentas poderosas para enfrentar desafios complexos do mundo real.

2. Tarefa, modelo, medição de desempenho e experiência

Para construir uma rede neural eficaz, você precisa considerar alguns componentes que definem seu desenvolvimento e seu sucesso. Entre eles estão identificar com clareza a tarefa que a rede vai executar, escolher um modelo adequado, estabelecer critérios de medição de desempenho e acumular experiência por meio dos dados de treinamento. 

Cada elemento tem um papel decisivo em quão bem a rede neural aprende e generaliza para dados novos. Veja o que cada um faz e por que importa:

  • Tarefa – O primeiro passo para construir uma rede neural é definir a tarefa que você quer que ela execute. Pode ser desde classificação de imagens até processamento de linguagem natural. Definir a tarefa ajuda a desenhar a arquitetura de rede adequada e a escolher os dados de treinamento certos.
  • Modelo – A escolha do modelo é um aspecto importante da construção de uma rede neural. Tipos diferentes de modelo de rede neural (como redes feedforward, redes convolucionais e redes recorrentes) servem para tarefas diferentes. Escolher o modelo certo melhora o desempenho da rede e sua capacidade de resolver o problema desejado.
  • Medição de desempenho – Depois de definir a tarefa, você precisa determinar como medir o desempenho da rede neural. As métricas podem incluir acurácia, precisão, recall ou F1-score, dependendo do problema específico que você quer resolver.
  • Experiência – O passo final é fornecer dados de treinamento à rede neural, permitindo que ela aprenda e melhore seu desempenho ao longo do tempo. Essa fase de experiência é decisiva para construir um modelo de alto desempenho, capaz de generalizar bem para dados novos, nunca vistos.

Neste post, o foco é o primeiro modelo de neurônio artificial, então vamos definir a tarefa como um problema de regressão linear. 

O problema de regressão linear é aquele em que usamos o neurônio artificial para representar funções. Em vez de usar uma função como y = mx + b, apresentamos os dados ao neurônio artificial e deixamos que ele descubra os valores adequados dos coeficientes m e b que representam o problema. Voltamos a isso na Seção 4.

Na próxima seção, vamos descrever a modelagem do neurônio artificial. Não se assuste com a matemática: não vamos entrar muito fundo nela, prometo!

Leia também: Glossário de IA e machine learning: termos-chave para empresas modernas

3. A primeira representação de um neurônio artificial (Perceptron)

A base das redes neurais modernas remonta à primeira representação matemática de um neurônio artificial, conhecida como Perceptron. Apresentado por Warren McCulloch e Walter Pitts em 1943, esse modelo simples e poderoso imita a forma como neurônios biológicos processam informação.

Ao receber entradas ponderadas, aplicar um bias e passar o resultado por uma função de ativação, o perceptron se torna o bloco fundamental de arquiteturas de rede neural mais avançadas. 

Esta seção explora os componentes principais do perceptron e como cada um contribui para sua capacidade de decidir. 

A Equação 1 é o modelo matemático do neurônio artificial:

Fórmula matemática da saída de um neurônio artificial

Onde: 

y é a saída,

phi é a função de ativação,

X é o vetor que contém todas as entradas,

W é o vetor que contém todos os pesos,

b é o bias

Da álgebra linear, podemos descrever o produto vetorial como:

Representação gráfica do Perceptron, o primeiro neurônio artificial

Onde: 

n é o número total de entradas e pesos.

  • Entradas – A primeira representação de um neurônio artificial, proposta por McCulloch e Pitts, recebe um conjunto de entradas (x1, x2, …, xn) e atribui um peso (w1, w2, …, wn) a cada uma delas.
  • Soma ponderada – O neurônio então calcula a soma ponderada das entradas, ou seja, a soma dos produtos de cada entrada pelo peso correspondente, como mostra a Equação 2.
  • Bias – O bias, representado pelo parâmetro b, é somado à soma ponderada antes da aplicação da função de ativação. Isso permite que o neurônio desloque seu limiar de ativação, viabilizando fronteiras de decisão mais complexas.
  • Função de ativação – Por fim, o neurônio aplica uma função de ativação à soma ponderada, como a função degrau ou a sigmoide, para determinar sua saída. Falamos sobre funções de ativação mais adiante.
Esquema de um neurônio artificial com entradas, pesos e saída
Representa o neurônio que McCulloch e Pitts usaram como inspiração para desenvolver o modelo do Perceptron.
Equação do neurônio artificial com pesos e viés
Representação visual da Equação 1, que facilita a comparação com um neurônio real.  

A seguir, vamos descrever como usar esse modelo para resolver um problema de regressão linear. Também mostramos como medir o erro do modelo e treiná-lo para reduzir esse erro.

4. O problema de regressão

Como descrito na Seção 2, a primeira tarefa que vamos atribuir ao nosso neurônio artificial é um problema de regressão. Para simplificar a representação, podemos remover a função de ativação e manter apenas uma entrada. 

Simplificação da equação usada no problema de regressão
Representação simplificada de um neurônio artificial

Antes de aprofundar em regressão linear, vale responder algumas perguntas:

O que é regressão linear?

Regressão linear é um algoritmo fundamental de machine learning, usado para modelar a relação entre uma variável dependente e uma ou mais variáveis independentes. O objetivo é encontrar a reta que melhor se ajusta aos dados, minimizando a distância entre os pontos e a linha.

Quais são as aplicações da regressão linear?

A regressão linear tem muitas aplicações: prever vendas, projetar preços de ações e analisar a relação entre diversos fatores em estudos sociais e econômicos. É uma ferramenta poderosa para entender e quantificar as relações entre variáveis.

Como implementar regressão linear?

Para implementar regressão linear, precisamos definir a equação do modelo, na forma y = mx + b, em que y é a variável dependente, x é a variável independente, m é o coeficiente angular e b é o ponto em que a reta corta o eixo y. Depois usamos técnicas de otimização para encontrar os valores de m e b que melhor se ajustam aos dados.

Quais são as limitações da regressão linear?

Apesar de ser um algoritmo prático, a regressão linear tem limitações. Ela pressupõe uma relação linear entre as variáveis, o que nem sempre é verdade. Também é sensível a outliers e pode ter desempenho ruim diante de relações complexas e não lineares.

Leia também: Usando regressão no desenvolvimento de IA.

Definimos a tarefa e o modelo com que vamos trabalhar, mas faltam dois passos para tudo se encaixar. Vamos ver como medir os resultados do modelo e como treiná-lo. 

Existem diferentes métodos e algoritmos para medir e treinar neurônios artificiais. Eles ficam mais complexos conforme os problemas que enfrentamos ficam mais difíceis. Mas, para entender como tudo se conecta, vamos usar o Mean Square Error (MSE) e o algoritmo de backpropagation para resolver nosso problema. 

4.1 Medindo o erro

Para medir o erro do modelo, usamos uma função de perda ou de custo que quantifica a diferença entre os valores previstos e os valores reais da variável dependente. O objetivo é minimizar essa função ajustando o coeficiente angular e o ponto de interceptação em y. A função de perda mais usada em regressão linear é o Mean Squared Error (MSE), que calcula a média das diferenças ao quadrado entre valores previstos e reais, como descrito na Equação 3. 

Fórmula do erro quadrático médio usada para medir o erro do modelo

Onde: Y é o valor previsto e Ŷ é o valor de referência do conjunto de dados de treinamento.
Certo, e o que fazemos com o valor do MSE?

Usamos esse valor para ajustar o peso e o bias do modelo, o que o leva a prever valores mais próximos do valor de referência do conjunto de dados. Para isso, usamos o algoritmo de backpropagation e uma série de hiperparâmetros que ajudam a treinar o modelo. 

4.2 Treinando o modelo

Neurônios artificiais são treinados pelo ajuste dos pesos e dos bias associados a eles, em um processo chamado backpropagation. Durante o treinamento, os dados de entrada passam pela rede neural e a saída é comparada com a saída desejada.

A diferença entre as duas é usada para calcular a perda e, em seguida, os pesos e os bias são ajustados para minimizá-la com algoritmos de otimização como o gradiente descendente. Esse processo se repete de forma iterativa até a rede atingir uma acurácia satisfatória.

O algoritmo Gradient Descent em machine learning serve para minimizar a função de custo e encontrar o conjunto ótimo de pesos, seguindo a descida mais íngreme na direção negativa do gradiente. 

A cada iteração, os parâmetros são atualizados subtraindo o gradiente multiplicado por uma learning rate, um hiperparâmetro que determina o tamanho do passo. O processo se repete até os critérios de convergência serem atendidos. 

A equação do gradiente descendente aparece na Equação 4, em que 𝛻F(y) é o gradiente da função de custo, é a learning rate e w é o peso do modelo.

Fórmula de atualização dos pesos durante o treinamento da rede neural

A Figura 4 apresenta o processo de convergência do peso para um valor mínimo usando o gradiente descendente. Vale destacar alguns pontos:

  • Learning rate: o hiperparâmetro learning rate não tem um valor exato. Ele precisa ser escolhido empiricamente, testando valores diferentes e observando qual entrega o melhor resultado. Uma dica é usar valores bem pequenos, como 0.0001 ou 0.00001, porque valores altos fazem o algoritmo passar longe do mínimo local, pular o valor ótimo do peso e explodir rapidamente. 
  • Epochs: o número de epochs é a quantidade de vezes que queremos que o algoritmo de backpropagation percorra o modelo para ajustar os pesos e o bias. As epochs são outro hiperparâmetro que precisa ser escolhido por quem desenvolve o modelo. A dica aqui é testar: se o treinamento for curto e o número de epochs for baixo, o modelo não aprende bem e não generaliza as saídas corretamente (fenômeno conhecido como underfit do modelo). Já se o número de epochs for alto demais, o modelo fica especializado demais nos dados de treinamento e perde a capacidade de generalizar (o que leva ao fenômeno conhecido como overfit do modelo).
Gráfico do gradiente descendente convergindo para o mínimo da função de erro
Representação gráfica do algoritmo Gradient Descendent aplicado a um modelo para encontrar o valor mínimo de peso que otimiza a função de custo.

Hoje já existem algoritmos que ajudam a escolher os melhores valores de hiperparâmetros para Perceptrons. Aqui, no entanto, apresentamos um exemplo desenvolvido do zero, sem a ajuda de nenhum framework ou biblioteca. Esse processo é essencial para entender como treinamos redes neurais maiores. Os algoritmos são mais sofisticados na hora de reduzir o tempo de treinamento, mas o processo é o mesmo. 

Leia também: As decisões de arquitetura de dados que realmente importam

4.3 Mostre o código

Depois de toda essa teoria e matemática, vamos ao código para entender como desenvolver um neurônio artificial simplificado que resolve uma tarefa de regressão linear. Todo o código apresentado aqui está disponível neste repositório.

Este código em Python implementa uma rede neural simples, com um único neurônio, capaz de aprender uma relação linear entre entradas e saídas. Ele inclui métodos para propagação direta, cálculo da perda e treinamento com gradiente descendente. Vamos destrinchar passo a passo.

import random

class NeuralNetwork:
	def __init__(self):
    	  # Random initialize weight
    	  self.weight = random.random()
    	  self.bias = random.random()

	def forward(self, _input: float | int):
    	  # Calculate the weighted sum
    	  output = self.weight * _input + self.bias
    	  return output

	def predict(self, X: [float]) -> [float]:
    	  Y_predicted = []
    	  for x in X:
          prediction = self.forward(x)
          Y_predicted.append(prediction)
    	  return Y_predicted

	@staticmethod
	def compute_loss(predicted_output: float, target: float) -> float:
    	  # In this example we used Mean Square Error (MSE)
    	  return (predicted_output - target) ** 2

	def compute_total_loss(self, targets: [float], predicted_outputs: [float]) -> float:
    	  # In this example we used Mean Square Error (MSE)
    	  total_loss = 0
    	  for target, predicted_output in zip(targets, predicted_outputs):
          total_loss += (predicted_output - target) ** 2

    	  return total_loss

	def compute_loss_derivative(self, predicted_output: float, target: float) -> float:
    	  return (predicted_output - target) * 2

	def train(self, training_sample: [float], learning_rate=0.01, epochs=1000):
    	  for epoch in range(1, epochs + 1):
          total_loss = 0

          for _input, target in training_sample:
            # Forward Pass
            predicted_output = self.forward(_input)
            # print(_input, target, predicted_output)

            # Calculate Loss
            loss = self.compute_loss(predicted_output, target)
            total_loss += loss

            # Backward Pass (Calculate gradients)
            loss_derivative_value = self.compute_loss_derivative(predicted_output, target)
            gradient = loss_derivative_value * _input
            bias_gradient = loss_derivative_value

            # Update Weights and Bias
            self.weight -= learning_rate * gradient
            self.bias -= learning_rate * bias_gradient

          # Print loss every 100 epochs
          if epoch % 100 == 0:
            print(f"Epoch: {epoch}, Loss: {total_loss:.4f}, Weight: {self.weight:.4f}, Bias: {self.bias:.4f}")Code language: Python (python)

A classe NeuralNetwork começa com um construtor (__init__), que inicializa um peso e um bias. Esses valores são atribuídos aleatoriamente com random.random(), garantindo que o modelo comece com parâmetros diferentes de zero.

import random

class NeuralNetwork:
	def __init__(self):
    	 # Randomly initialize weight and bias
    	 self.weight = random.random()
    	 self.bias = random.random()Code language: Python (python)

A função forward executa um forward pass pelo neurônio. Ela calcula a soma ponderada da entrada e adiciona o bias.

def forward(self, _input: float | int):
	# Calculate the weighted sum
	output = self.weight * _input + self.bias
	return outputCode language: Python (python)

O método predict recebe uma lista de entradas e devolve as saídas correspondentes. Ele apenas aplica a função forward a cada valor de entrada.

def predict(self, X: [float]) -> [float]:
	Y_predicted = []
	 for x in X:
    	  prediction = self.forward(x)
    	  Y_predicted.append(prediction)
	return Y_predictedCode language: Python (python)

A compute_loss function calcula o Mean Squared Error (MSE) para uma única previsão. A diferença ao quadrado garante que os erros sejam sempre positivos e penaliza desvios maiores com mais peso.

@staticmethod
def compute_loss(predicted_output: float, target: float) -> float:
	return (predicted_output - target) ** 2Code language: Python (python)

A função compute_total_loss calcula a perda total em um conjunto de dados somando os erros quadráticos individuais. Isso ajuda a acompanhar o desempenho do modelo ao longo de vários pontos.

def compute_total_loss(self, targets: [float], predicted_outputs: [float]) -> float:
	total_loss = 0
	for target, predicted_output in zip(targets, predicted_outputs):
    	total_loss += (predicted_output - target) ** 2
	return total_lossCode language: Python (python)

A função compute_loss_derivative calcula a derivada da função de perda em relação à saída prevista. Como estamos usando MSE, a derivada é: 

Trecho de código Python para treinar uma rede neural do zero

Essa derivada é essencial para o gradiente descendente atualizar os parâmetros do modelo.

def compute_loss_derivative(self, predicted_output: float, target: float) -> float:
	return (predicted_output - target) * 2Code language: Python (python)

Por fim, a função train treina o neurônio artificial usando gradiente descendente:

  1. Forward Pass: calcula previsões para cada entrada.
  2. Cálculo da perda: avalia o quanto as previsões estão distantes dos valores reais.
  3. Backward Pass: usa a derivada da perda para calcular os gradientes.
  4. Atualização dos parâmetros: ajusta weight e bias usando a learning rate.

A perda é impressa a cada 100 epochs para acompanhar o progresso do treinamento.

def train(self, training_sample: [float], learning_rate=0.01, epochs=1000):
	for epoch in range(1, epochs + 1):
    	total_loss = 0

    	for _input, target in training_sample:
        	# Forward Pass
        	predicted_output = self.forward(_input)

        	# Calculate Loss
        	loss = self.compute_loss(predicted_output, target)
        	total_loss += loss

        	# Backward Pass (Calculate gradients)
        	loss_derivative_value = self.compute_loss_derivative(predicted_output, target)
        	gradient = loss_derivative_value * _input
        	bias_gradient = loss_derivative_value

        	# Update Weights and Bias
        	self.weight -= learning_rate * gradient
        	self.bias -= learning_rate * bias_gradient

    	# Print loss every 100 epochs
    	if epoch % 100 == 0:
        	print(f"Epoch: {epoch}, Loss: {total_loss:.4f}, Weight: {self.weight:.4f}, Bias: {self.bias:.4f}")Code language: Python (python)

Vamos a um exemplo para deixar claro como usar essa classe. O código abaixo foi extraído do mesmo repositório, e a versão completa está no Jupyter Notebook linear_regression.ipynb.

Para ilustrar o processo de regressão linear, o notebook gera dados sintéticos:

  • Valores de entrada (X): um conjunto de valores aleatórios.
  • Valores de saída (Y): gerados a partir de uma relação linear com X, normalmente na forma Y = mX + b + noise, em que m é o coeficiente angular, b é o intercepto e noise adiciona variabilidade para simular dados do mundo real.

Esses dados sintéticos funcionam como um ambiente controlado para demonstrar a mecânica da regressão linear. Adicionamos ruído aleatório para criar dispersão entre os valores. Sem isso, teríamos apenas uma reta ascendente com coeficiente angular igual a 5.

def regression_function(samples=100) -> ([int], [float]):
    X = []
    Y = []
    for x in range(samples):
        # y = 5 * x + 1
        y = 5 * x + random.uniform(-100, 100)
        X.append(x)
        Y.append(y)

    return X, YCode language: Python (python)
Gráfico dos dados sintéticos usados para treinar a rede neural
Mostra o gráfico dos dados sintéticos gerados, para entender de onde o neurônio artificial vai aprender.

Em seguida, instanciamos um neurônio artificial e enviamos alguns dados para verificar se ele consegue prever a saída. A Figura 6 mostra o resultado, com a linha vermelha formada pelos valores de saída, ou previsão, do neurônio artificial.

from perceptron.linear_regression import NeuralNetwork

nn = NeuralNetwork()
Y_predicted = nn.predict(X)
print_prediction_function(X, Y, Y_predicted)Code language: Python (python)
Gráfico da reta ajustada pela rede neural sobre os dados de treino

Como você pode ver, a linha vermelha não reflete a realidade dos dados de treinamento, o que significa que precisamos treinar o neurônio artificial. O próximo passo é chamar o método train com epochs = 100000 e learning_rate = 0.00001. Isso aplica o algoritmo de backpropagation 100000 vezes e atualiza o peso e o bias com um passo muito pequeno, o que ajuda o modelo a convergir para valores ótimos que representam os dados. Os valores finais obtidos após o treinamento foram:

  • Epoch: 100000, 
  • Loss: 367628.9078, 
  • Weight: 5.4246, 
  • Bias: -18.1625

Vale notar que o valor de Loss é acumulado ao longo do processo de treinamento. Nos primeiros passos, o erro é enorme, mas, conforme o neurônio atualiza seu peso e seu bias, o erro se estabiliza.

population = [*zip(X, Y)]
training_sample = [*zip(X, Y)]
learning_rate = 0.00001
epochs = 100000
nn.train(training_sample, learning_rate, epochs)Code language: Python (python)

A Figura 7 apresenta o resultado final depois que o neurônio artificial treinado prevê os valores.

Curva de perda ao longo das épocas de treinamento da rede neural

Para fechar

Agora podemos usar esse neurônio artificial treinado para prever novos valores. Dá para expandir o exemplo para uma tarefa de previsão de preço de imóveis, com dezenas ou centenas de variáveis de entrada. Precisamos usá-las para definir o valor final de uma casa a partir de seus atributos. 

Vale destacar que não cobrimos aspectos importantes do processo de treinamento, como separar 80% dos dados para treino, usar os 20% restantes para validação ou limpar e padronizar os dados. Esse tipo de técnica ajuda no desenvolvimento do modelo e tem suas armadilhas.

Mas você pode contar com o time da Cheesecake Labs para ajudar no desenvolvimento de modelos de rede neural. Temos engenheiros especializados prontos para mergulhar nos dados e criar modelos que resolvem os problemas do seu negócio.

Em um próximo post, vamos olhar para o problema de classificação. Até lá, confira outros conteúdos e relatórios que explicam nossa abordagem para machine learning e desenvolvimento de aplicações com IA na Cheesecake Labs:

Se você tem uma ideia de projeto que se beneficiaria de uma rede neural, mande uma mensagem e vamos conversar. Vai ser um prazer ajudar a tirar suas ideias do papel.

agende uma conversa com os especialistas da cheesecake labs

FAQ

O que são redes neurais e para que servem?

Redes neurais são um tipo de algoritmo de machine learning inspirado na estrutura e no funcionamento do cérebro humano, formadas por nós interconectados (“neurônios”) que aprendem a executar tarefas analisando grandes volumes de dados. Elas têm aplicações como reconhecimento de imagem, processamento de linguagem natural, reconhecimento de fala, análise preditiva, detecção de fraude, previsão de demanda, recomendações personalizadas e sistemas autônomos.

Quais componentes precisam ser considerados para construir uma rede neural eficaz?

Quatro componentes: a tarefa que a rede vai executar (por exemplo, classificação de imagens ou processamento de linguagem natural), o modelo adequado (como redes feedforward, convolucionais ou recorrentes), a medição de desempenho (métricas como acurácia, precisão, recall ou F1-score) e a experiência, que consiste em fornecer dados de treinamento para que a rede aprenda e generalize para dados novos.

O que é o Perceptron e como ele funciona?

O Perceptron é a primeira representação matemática de um neurônio artificial, apresentado por Warren McCulloch e Walter Pitts em 1943. Ele recebe um conjunto de entradas, atribui um peso a cada uma, calcula a soma ponderada, soma o bias (que permite deslocar o limiar de ativação) e aplica uma função de ativação, como a função degrau ou a sigmoide, para determinar a saída.

O que é regressão linear e quais são suas limitações?

Regressão linear é um algoritmo de machine learning usado para modelar a relação entre uma variável dependente e uma ou mais variáveis independentes, encontrando a reta (y = mx + b) que melhor se ajusta aos dados. Suas limitações são: pressupor uma relação linear entre as variáveis, ser sensível a outliers e ter desempenho ruim diante de relações complexas e não lineares.

Como o modelo é treinado e o que são learning rate e epochs?

O erro é medido com o Mean Squared Error (MSE) e o modelo é treinado com backpropagation, ajustando pesos e bias por meio do gradiente descendente para minimizar a função de custo. A learning rate é um hiperparâmetro que define o tamanho do passo de atualização, escolhido empiricamente, com a dica de usar valores bem pequenos, como 0.0001 ou 0.00001. Epochs é o número de vezes que o backpropagation percorre o modelo: poucas epochs podem causar underfit e epochs demais podem causar overfit.