Existe um único procedimento capaz de gerar toda loss function de Machine Learning. No artigo anterior, nós o aplicamos uma única vez: mostramos que minimizar o MSE equivale a maximizar a verossimilhança sob ruído gaussiano. Hoje vamos aplicá-lo novamente, trocando um único elemento, a distribuição, e ver a binary cross-entropy surgir naturalmente, sem nenhuma escolha arbitrária no caminho.
Este é o terceiro artigo da série Matemática para Machine Learning. No primeiro, construímos a regressão linear
; no segundo, derivamos o gradient descent e descobrimos que o MSE é consequência de uma hipótese estatística. Agora atravessamos a fronteira mais importante do curso: sair da regressão, em que
é um número contínuo, e entrar na classificação, em que
é uma resposta binária. A notação segue a convenção padrão da literatura de Machine Learning.
Código do Artigo
Acesse o código-fonte deste artigo gratuitamente.
Informe seu email para acessar o código:
✓ Seu código está pronto!
Abrir no Google Colab →O procedimento que gera toda loss function
Vale recapitular o final do artigo anterior, porque ele é o motor de tudo o que vem a seguir. Ao assumir que os dados reais são a reta mais um ruído gaussiano,
, a pergunta “qual reta melhor explica os dados?” virou um problema de máxima verossimilhança: encontrar os parâmetros que tornam os dados observados os mais prováveis. Desse raciocínio caiu o MSE, sem que ninguém o escolhesse.
O que fica desse resultado é um procedimento que reutilizaremos integralmente:
- Escolher uma distribuição de probabilidade para os dados.
- Escrever a verossimilhança como produto sobre os
exemplos. - Aplicar o logaritmo, transformando produto em soma.
- Inverter o sinal e chamar o resultado de loss.
Com ruído gaussiano, esse procedimento produz o MSE. A pergunta natural é: o que acontece quando a gaussiana deixa de ser a distribuição certa?
Quando a resposta é sim ou não
Até aqui,
era um número contínuo, como o preço de um imóvel. Mas boa parte dos problemas reais pede outro tipo de resposta. Esse e-mail é spam (
) ou não (
)? Esse tumor é maligno ou benigno? Esse cliente vai cancelar a assinatura ou ficar? Agora
só pode valer
ou
, e não faz sentido um modelo produzir
. O que queremos prever é a probabilidade de a resposta ser
.
A tentação é reaproveitar a regressão linear com um corte: “se
passar de
, digo que é classe 1″. Isso falha por duas razões. Primeiro, a reta produz qualquer valor de
a
, e probabilidade precisa viver entre
e
. Segundo, um único ponto muito distante, um outlier, entorta a reta inteira e desloca a fronteira de decisão. Precisamos de algo que comprima a saída linear para dentro do intervalo
.
No notebook, geramos um dataset sintético com duas features e duas classes parcialmente sobrepostas, o cenário que acompanha todo o artigo:

O compressor: a função sigmoid
A solução é compor a saída linear de sempre,
, com uma função que comprime a reta real inteira no intervalo
. Essa função é a sigmoid:
![]()
O comportamento dela nos extremos e no centro conta a história completa:
def sigmoid(z):
"""Comprime qualquer número real para o intervalo (0, 1)."""
return 1.0 / (1.0 + np.exp(-z))
for z in [-10, -2, 0, 2, 10]:
print(f"sigmoid({z:+3d}) = {sigmoid(z):.4f}")
sigmoid(-10) = 0.0000 sigmoid( -2) = 0.1192 sigmoid( +0) = 0.5000 sigmoid( +2) = 0.8808 sigmoid(+10) = 1.0000
A sigmoid funciona como um interruptor suave. Bem à esquerda, ela diz “quase certeza de que é classe 0”; bem à direita, “quase certeza de que é classe 1”; e em
ela fica exatamente em cima do muro,
. Esse ponto de indecisão é a fronteira de decisão, e vamos visualizá-la ao final.

O modelo de classificação fica, então,
. A saída deixou de ser um número qualquer e passou a ser a probabilidade de a classe ser
. E como só existem duas classes,
.
Bernoulli: a distribuição certa para sim ou não
Com o modelo pronto, voltamos ao procedimento. O passo um pede uma distribuição para os dados, como as que exploramos em distribuições estatísticas com Python, e uma variável que só assume
ou
segue uma distribuição de Bernoulli. Ela tem uma forma de escrita elegante, que junta os dois casos em uma fórmula única:
![]()
A fórmula parece um artifício engenhoso, mas é apenas um liga-desliga com expoentes. Se
, o segundo fator vira
e sobra
, a probabilidade de ser classe 1. Se
, o primeiro fator desliga e sobra
. Uma fórmula única para os dois casos, e é exatamente essa compactação que fará o logaritmo do próximo passo funcionar tão bem.
Aplicando o procedimento: nasce a binary cross-entropy
Agora é execução. A verossimilhança é o produto sobre os exemplos, assumindo independência:
![Rendered by QuickLaTeX.com \[L(w, b) = \prod_{i=1}^{m} \hat{p}^{(i)\, y^{(i)}} \big(1 - \hat{p}^{(i)}\big)^{1 - y^{(i)}}, \qquad \hat{p}^{(i)} = \sigma\big(w^\top x^{(i)} + b\big)\]](https://sigmoidal.ai/wp-content/ql-cache/quicklatex.com-245d24de2cf0c06a5979b0f543e7d073_l3.png)
O logaritmo transforma o produto em soma e derruba os expoentes, usando
e
:
![]()
Por fim, invertemos o sinal, porque maximizar a verossimilhança é minimizar o negativo dela, e dividimos por
para virar uma média:
![]()
Essa é a famosa binary cross-entropy (em português, entropia cruzada binária). Repare no que acabou de acontecer: ela não caiu do céu nem foi escolhida por conveniência. É o mesmo procedimento que gerou o MSE, com a única diferença de que trocamos “ruído gaussiano” por “distribuição de Bernoulli”. A loss mudou de forma porque a distribuição dos dados mudou. Mais nada.
Por que essa loss faz sentido
A fórmula intimida à primeira vista, mas o comportamento dela é muito intuitivo. Por causa do liga-desliga da Bernoulli, para cada exemplo sobra apenas um dos dois termos. Quando a resposta certa é
, sobra
: se o modelo acertou com confiança (
), a loss vai a zero; se errou gravemente (
), a loss dispara para o infinito. Quando
, sobra
, com o comportamento espelhado. A implementação é uma tradução direta da fórmula, com um detalhe numérico: o clip impede o
, que explodiria para infinito.
def bce(y, p_hat, eps=1e-12):
"""Binary cross-entropy média (a cost da classificação)."""
p_hat = np.clip(p_hat, eps, 1 - eps)
return -np.mean(y * np.log(p_hat) + (1 - y) * np.log(1 - p_hat))
print(f"acertou com confiança (y=1, p=0.99): loss = {bce(np.array([1.0]), np.array([0.99])):.4f}")
print(f"em cima do muro (y=1, p=0.50): loss = {bce(np.array([1.0]), np.array([0.50])):.4f}")
print(f"errou com confiança (y=0, p=0.99): loss = {bce(np.array([0.0]), np.array([0.99])):.4f}")
acertou com confiança (y=1, p=0.99): loss = 0.0101 em cima do muro (y=1, p=0.50): loss = 0.6931 errou com confiança (y=0, p=0.99): loss = 4.6052
O acerto confiante custa quase nada. A previsão neutra de
custa
, que é exatamente
. E o erro confiante custa
, quase sete vezes o valor do muro. A cross-entropy pune confiança errada de forma brutal: se o modelo diz “tenho 99% de certeza de que é classe 1” e a resposta era 0, a loss explode. É a função de custo dizendo que errar com convicção é imperdoável, enquanto o erro tímido é punido de leve. Isso leva o modelo a só ficar confiante quando há motivo.

Treinando e enxergando a fronteira de decisão
Falta treinar. O gradiente da BCE em relação aos parâmetros tem uma propriedade notável: apesar de a fórmula da loss ser completamente diferente da do MSE, a regra de atualização sai com exatamente a mesma forma da regressão linear, erro vezes entrada:
![]()
A derivação passo a passo dessas fórmulas, que envolve uma simplificação elegante da derivada da sigmoid, fica para o próximo artigo da série. Por ora, vamos usá-las com o mesmo gradient descent de sempre. O modelo é a sigmoid da saída linear, o gradiente traduz as fórmulas acima, e o laço de treinamento é idêntico ao da regressão linear:
def modelo(X, w, b):
"""Probabilidade prevista para a classe 1: sigmoid da saída linear."""
return sigmoid(X @ w + b)
def gradiente(X, y, w, b):
"""Derivadas parciais da BCE: a forma 'erro vezes entrada'."""
erro = modelo(X, w, b) - y
dw = X.T @ erro / len(y)
db = np.mean(erro)
return dw, db
def gradient_descent(X, y, w, b, alpha, n_iter):
"""Treina a regressão logística, retornando parâmetros e histórico da cost."""
historico = []
for _ in range(n_iter):
dw, db = gradiente(X, y, w, b)
w = w - alpha * dw # atualização simultânea
b = b - alpha * db
historico.append(bce(y, modelo(X, w, b)))
return w, b, historico
w, b, historico = gradient_descent(X, y, w=np.zeros(2), b=0.0, alpha=0.1, n_iter=3000)
BCE em w=0, b=0 (previsão neutra de 50%): 0.6931 w treinado = [1.610, 1.167] b treinado = -7.924 BCE final = 0.0941 acurácia = 0.965
Repare no ponto de partida. Com
e
, a saída linear é zero para todo exemplo, a sigmoid devolve
para todos, e a cost é exatamente o
do “em cima do muro” que medimos há pouco: o modelo não treinado é uma previsão neutra de
. O gradient descent derrubou essa cost para
, e o modelo passou a classificar corretamente
dos exemplos. O modelo prevê classe 1 quando
, o que acontece exatamente quando
. A fronteira de decisão é, portanto, a reta
, e podemos enxergá-la junto com a probabilidade prevista em cada região do plano:

A linha preta é o “em cima do muro” da sigmoid, os pontos em que o modelo atribui
para cada classe. Longe dela, nas regiões de azul ou vermelho intensos, o modelo está confiante, e a cross-entropy garantiu durante o treinamento que essa confiança só aparecesse onde os dados a sustentam. Esse modelo tem nome: regressão logística, criado há mais de meio século e até hoje o primeiro classificador a ser treinado em qualquer problema sério.
A lição que vale para o resto do curso é a mesma do artigo anterior, agora confirmada duas vezes: toda loss function é uma escolha de distribuição de probabilidade disfarçada. Sempre que você encontrar uma loss nova, a pergunta certa é “que distribuição essa loss assume sobre os dados?”. Existe uma resposta. Sempre há uma.
Takeaways
- Um único procedimento gera toda loss function: escolher a distribuição dos dados, escrever a verossimilhança, aplicar o logaritmo e inverter o sinal. Gaussiana gera o MSE; Bernoulli gera a cross-entropy.
- A sigmoid transforma a saída linear em probabilidade, comprimindo
no intervalo
, com a fronteira de decisão exatamente em
. - A cross-entropy pune a confiança errada de forma brutal: no experimento, o erro confiante custou
, contra
do acerto confiante, empurrando o modelo a calibrar a própria certeza. - O gradiente mantém a forma “erro vezes entrada” da regressão linear, e com ele a regressão logística atingiu
de acurácia, desenhando uma fronteira de decisão linear no plano.


















