Autoencoders: o Gargalo, a Reconstrução e o Latente
por Frank de Alcantara em 28/07/2026
Um sistema de inteligência artificial não percebe o mundo diretamente. Ele recebe medições: valores de pixels, amostras de áudio, leituras de sensores, identificadores de palavras ou estados fornecidos por outro programa. Ou seja, é chato, e burro como uma porta.
Índice da Série: Representações e Modelos de Mundo
- 1. Representações e Modelos de Mundo: o Mapa da Série
- 2. Autoencoders: o Gargalo, a Reconstrução e o Latente (Você está aqui)
- 4. Difusão Latente: Comprimir Antes de Gerar
- 5. SimCLR: Aprender por Contraste e Pagar pelos Negativos
- 6. BYOL: Aprender sem Negativos sem Entregar Tudo ao Colapso
- 7. DINO: Autodestilação e Objetos que Emergem da Atenção
- 8. MAE: Mascarar 75% da Imagem e Reconstruir o que Falta
- 9. Prever, não gerar: o template JEPA e o problema do colapso
Antes de classificar uma imagem, compreender uma frase ou escolher uma ação, o sistema precisa transformar essas medições em uma descrição interna em que as regularidades relevantes estejam acessíveis. Sem essa transformação, cada tarefa posterior teria de redescobrir, a partir dos dados brutos, o que pode ser ignorado e o que não pode ser perdido.
Chamaremos de representação essa descrição interna. Se $x$ é uma observação e $r$ é uma função de transformação, podemos escrever
\[z=r(x),\]na qual $z$ é a representação de $x$. A fórmula é curta, mas esconde escolhas importantes e determinantes para o sucesso do projeto.
Uma representação de uma imagem pode conservar cor e descartar posição; outra pode conservar contornos e descartar textura. Ambas são descrições menores ou mais organizadas da mesma observação, porém servem a tarefas diferentes.
Estudar representações é descobrir quais invariâncias, separações e perdas de informação a função $r$ introduz antes que um classificador, um planejador ou uma política receba os dados.
Uma representação, sozinha, ainda não é um modelo de mundo. Ela descreve uma observação. Um modelo de mundo acrescenta estrutura preditiva: relaciona partes ocultas, estados sucessivos ou consequências de ações. Em outras palavras, a representação responde como descrever o que foi observado?; o modelo de mundo tenta responder também o que não foi observado, o que pode acontecer depois e como uma intervenção altera esse futuro?. A passagem da primeira pergunta para a segunda fornece o esforço que faremos nesta série.
Representações podem ser simbólicas, probabilísticas, geométricas ou neurais; as redes neurais não receberam monopólio sobre o assunto por decreto. Receberam por direito e, talvez, por aplicabilidade computacional.
Nesta série, começaremos pela linhagem neural porque ela aprende a transformação $r$ diretamente dos dados por gradiente e porque é dessa linhagem que surgem os métodos que conduzirão ao JEPA e os algoritmos mais recentes.
Vamos começar por uma rede que reconstrói a própria entrada. Porque esse é o contrato neural mais simples em que a representação precisa mostrar serviço sem receber rótulos humanos. A entrada fornece simultaneamente o problema e o alvo, e qualquer restrição colocada entre os dois lados torna visível o que o modelo prefere conservar. Antes de estudar alvos lentos, pares contrastivos ou previsão de representações, convém observar essa escolha na sua forma menos ornamentada.
Não queremos ensinar uma rede a copiar. Queremos usar a obrigação de reconstruir para aprender, sem rótulos, uma representação que possa ser reutilizada.
Durante o treino, o decoder funciona como uma banca examinadora: ele cobra do código latente $z$ informação suficiente para recuperar $x$. Depois, podemos avaliar esse código como entrada para classificação, busca por semelhança ou detecção de anomalias; também podemos manter o decoder e fazer de $z$ o espaço de trabalho de um modelo gerativo, como faremos com a difusão latente no próximo artigo. A reconstrução é o instrumento de aprendizagem. A representação é o resultado que pretendemos levar adiante.
O caso linear nos dará um controle conhecido antes que as não linearidades entrem em cena. Quando obrigamos uma reconstrução linear a atravessar poucas dimensões, encontramos a mesma geometria da PCA, de principal component analysis (análise de componentes principais): escolher um subespaço que conserve o máximo possível da variação dos dados. Vamos construir essa ideia antes de usá-la, porque uma sigla não deve aparecer para resolver uma derivação que a leitora ainda não teve a oportunidade de compreender.
A astuta leitora deve, portanto, começar desconfiando de uma rede neural cuja tarefa seja receber uma entrada $x$ e devolver a mesma entrada $x$. Se este for todo o contrato, a função identidade vence a batalha antes que o primeiro gradiente seja calculado:
\[\hat{x}=x.\]Seria uma façanha digna de um bom departamento de marketing: milhões de parâmetros para descobrir que $x=x$. Não é um esforço simples de justificar.
O problema se torna intelectualmente útil quando estragamos essa saída. Estreitamos o caminho, corrompemos a entrada ou regularizamos a representação. Quando copiar deixa de ser possível, a rede precisa decidir o que conservar para que a reconstrução continue aceitável.
Este é o território dos autoencoders. Eles abrem Representações e Modelos de Mundo porque expõem, sem qualquer gentileza, a pergunta que acompanhará todos os artigos seguintes: o que a função objetivo obriga uma representação a conservar, e quanto estamos dispostos a pagar pelo que ela esquece?
1. Encoder, latente e decoder
Um autoencoder possui duas funções. O encoder (codificador) $f_\theta$ transforma a entrada $x\in\mathbb{R}^D$ em uma representação latente $z\in\mathbb{R}^d$:
\[z=f_\theta(x).\]O decoder (decodificador) $g_\phi$ percorre o caminho inverso e produz uma reconstrução $\hat{x}\in\mathbb{R}^D$:
\[\hat{x}=g_\phi(z)=g_\phi(f_\theta(x)).\]Chamaremos de $\mathcal{D}(x,\hat{x})\ge 0$ a função que mede a dívida entre a entrada e sua reconstrução. Para vetores reais, uma escolha comum é o MSE, de mean squared error (erro quadrático médio), cuja construção estatística, relação com a média e convenções de denominador são deduzidas no segundo artigo da série Estatística Orientada à Ciência de Dados. Não tenha pressa, eu espero. Quando voltar temos:
\[\mathcal{L}_{\mathrm{rec}} =\frac{1}{D}\sum_{j=0}^{D-1}(x_j-\hat{x}_j)^2.\]Os parâmetros $\theta$ e $\phi$ são ajustados para minimizar a perda média sobre o conjunto de treino. Nenhum ser humano precisa escrever um rótulo, pois o próprio $x$ fornece o alvo. Temos, portanto, uma forma de self-supervised learning (aprendizado autossupervisionado) por reconstrução.
Considere que $d$ conta quantas coordenadas atravessam a representação latente $z$, enquanto $D$ conta quantas coordenadas existem na entrada $x$. Se $d\ge D$, as redes forem expressivas e não houver outra restrição, o sistema pode aproximar a identidade. A representação latente não precisa organizar o mundo; basta transportar informação suficiente para copiá-lo. Um gargalo subcompleto impõe $d\lt D$. A passagem fica estreita, mas a atenta leitora não deve confundir estreiteza com inteligência: o modelo conservará aquilo que mais reduzir a distância escolhida, não aquilo que nós consideramos semanticamente importante.
1.1 De memória autoassociativa a autoencoder
A ideia nasceu antes do nome estabilizar na comunidade acadêmica. No capítulo Learning Internal Representations by Error Propagation, de 1986, Rumelhart, Hinton e Williams treinaram redes a reproduzir padrões na saída e mostraram que as unidades ocultas podiam adquirir códigos internos. Dois anos mais tarde, Hinton e McClelland, em 1988, chamaram a tarefa de reconstruir o vetor visível a partir do oculto de teste da qualidade da representação. A pesquisa não parou.
Baldi e Hornik, em 1989, resolveram matematicamente o caso linear e ligaram o mínimo da reconstrução aos subespaços principais. Finalmente, Hinton e Zemel, em 1993, usaram explicitamente o termo autoencoder ao relacionar reconstrução, comprimento de descrição e custo de codificação. E a lenda nasceu.
Essa genealogia esclarece o mecanismo. O autoencoder não surgiu como uma rede que comprime por magia, mas como uma memória autoassociativa com uma representação interna treinável. Durante o treino, ele recebe uma entrada $x$ e é penalizado quando a reconstrução $\hat{x}$ difere dela. Para reduzir esse erro, o encoder precisa produzir um código $z$ que permita ao decoder reconstruir $x$. A arquitetura e a regularização dificultam a cópia direta; diante dessas restrições, o treinamento favorece as informações cuja ausência aumentaria mais o erro de reconstrução.
Há ainda uma diferença entre dimensão e quantidade de informação.
Um vetor real unidimensional possui, em matemática exata, infinitos valores possíveis. Uma rede suficientemente irregular poderia codificar muitos pontos de treino distintos em um único escalar e decodificá-los por memorização. A desigualdade $d\lt D$ limita a dimensão geométrica do código, mas não impõe sozinha uma taxa finita em bits. Para falar em compressão física precisamos também especificar precisão numérica, ruído, suavidade, distribuição dos dados ou um custo de código. Eis por que gargalo, regularização e generalização devem ser discutidos juntos.
1.2 Exercícios de lápis e papel
1. Para $D=6$ e $d=2$, escreva os formatos de $x$, $z$, de uma matriz linear de codificação $W_e$ e de uma matriz linear de decodificação $W_d$.
Solução: Se tratarmos $x$ e $z$ como vetores coluna, teremos
\[x\in\mathbb{R}^{6}, \qquad z\in\mathbb{R}^{2}.\]Para que $z=W_ex$, a matriz de codificação deve transformar seis coordenadas em duas:
\[W_e\in\mathbb{R}^{2\times6}.\]Para que $\hat{x}=W_dz$ retorne ao espaço de entrada, a matriz de decodificação deve ter o formato
\[W_d\in\mathbb{R}^{6\times2}.\]2. Considere $x=(2,-1)$, $W_e=[\,1\;\;2\,]$ e $W_d=[\,1\;\;0\,]^\top$. Calcule $z$, $\hat{x}$ e o MSE por coordenada.
Solução: O símbolo $\top$ indica transposição: em uma matriz $A$, vale
\[(A^\top)_{ij}=A_{ji}\]Neste caso, ele transforma a linha $[\,1\;\;0\,]$ na coluna $[\,1\;\;0\,]^\top$. Escrevendo $x$ como vetor coluna, o código escalar é
\[z=W_ex =\begin{bmatrix}1&2\end{bmatrix} \begin{bmatrix}2\\-1\end{bmatrix} =2-2=0.\]A reconstrução será
\[\hat{x}=W_dz =\begin{bmatrix}1\\0\end{bmatrix}0 =\begin{bmatrix}0\\0\end{bmatrix}.\]O MSE por coordenada vale
\[\frac{(2-0)^2+(-1-0)^2}{2} =\frac{4+1}{2} =2{,}5.\]3. Mostre que escolher $W_e=I_D$ e $W_d=I_D$ produz perda de reconstrução zero quando $d=D$.
Solução: Com $d=D$, as duas matrizes identidade possuem formato $D\times D$. Portanto,
\[z=W_ex=I_Dx=x\]e
\[\hat{x}=W_dz=I_Dx=x.\]Cada diferença $x_j-\hat{x}_j$ é zero. Logo,
\[\mathcal{L}_{\mathrm{rec}} =\frac{1}{D}\sum_{j=0}^{D-1}(x_j-\hat{x}_j)^2 =0.\]4. Dê dois vetores distintos que poderiam receber o mesmo código em um encoder não injetivo e explique por que o decoder não pode reconstruir ambos exatamente a partir desse código.
Solução: Considere o encoder $f(x_0,x_1)=x_0$. Os vetores
\[x_a=(1,0) \qquad\text{e}\qquad x_b=(1,1)\]são distintos, mas recebem o mesmo código:
\[f(x_a)=f(x_b)=1.\]Um decoder determinístico recebe somente o valor $1$ e, por isso, precisa produzir a mesma saída nos dois casos. Reconstruir $x_a$ exigiria $g(1)=x_a$, enquanto reconstruir $x_b$ exigiria $g(1)=x_b$. Como $x_a\ne x_b$, as duas igualdades não podem ser satisfeitas simultaneamente.
5. Um latente possui $64$ valores FP32. Calcule sua capacidade física em bytes e compare com uma entrada de $784$ valores FP32, informando a razão entre os dois tamanhos.
Solução: Cada valor FP32 ocupa quatro bytes. O latente ocupa
\[64\cdot4=256\ \text{bytes},\]enquanto a entrada ocupa
\[784\cdot4=3136\ \text{bytes}.\]A razão entre o tamanho do latente e o tamanho da entrada é
\[\frac{256}{3136} =\frac{64}{784} =\frac{4}{49} \approx0{,}0816.\]O latente ocupa aproximadamente $8{,}16\%$ do espaço da entrada; de modo equivalente, a entrada é $12{,}25$ vezes maior.
2. O caso linear conduz à PCA
Vamos retirar ativações, vieses e qualquer oportunidade de a arquitetura esconder o mecanismo. Antes de comparar um autoencoder linear com PCA, porém, precisamos construir a técnica estatística que servirá de referência. A pergunta que precisamos responder é concreta: qual subespaço linear de dimensão $d$ conserva a maior parte da variação observada em dados com $D$ coordenadas?
2.1 O problema que a PCA resolve
Quando descrevemos cada observação com muitas variáveis, o número de coordenadas pode ser grande sem que os dados realmente ocupem todas as direções disponíveis. Sensores próximos podem responder ao mesmo fenômeno; pixels vizinhos tendem a variar juntos; medidas de tamanho frequentemente crescem ou diminuem em conjunto. Nesses casos, as coordenadas podem carregar informação parcialmente redundante. A nuvem de observações ocupa um espaço de dimensão alta, mas pode se concentrar nas proximidades de uma reta, de um plano ou de outro conjunto com bem menos dimensões.
O problema da PCA, de principal component analysis (análise de componentes principais), é aproximar essa nuvem por um subespaço de menor dimensão de forma sistemática. A técnica primeiro remove a posição média da nuvem e, a partir dos dados centralizados, procura um subespaço linear de menor dimensão. Em vez de escolher algumas variáveis originais e descartar as restantes, ela constrói novas direções como combinações lineares dessas variáveis. A primeira direção acompanha o eixo ao longo do qual as observações mais diferem entre si; cada direção seguinte procura a maior variação ainda não representada e permanece perpendicular às anteriores. Ordenadas dessa maneira, as novas coordenadas permitem conservar apenas as primeiras direções e obter uma descrição de menor dimensão que preserve a maior parcela possível da variação observada. Sob a perda quadrática que adotaremos, projetar os dados centralizados sobre essas direções produz o menor erro de reconstrução entre todos os subespaços de igual dimensão.
Essa resposta é geométrica, não semântica.
A PCA não recebe rótulos nem informação sobre quais variáveis determinam uma decisão; por si só, também não estabelece se a variação preservada corresponde a sinal relevante ou a ruído. Ela resolve uma pergunta mais restrita: se precisarmos representar cada observação por poucas coordenadas lineares, quais direções conservarão a maior parcela da variação observada?
Agora podemos transformar essa pergunta em um problema matemático preciso.
Considere $n$ observações $x^{(0)},x^{(1)},\ldots,x^{(n-1)}\in\mathbb{R}^D$. O número $n$ conta as observações e $D$ conta as variáveis medidas em cada uma. Primeiro calculamos o vetor de médias $\mu\in\mathbb{R}^D$
\[\mu=\frac{1}{n}\sum_{i=0}^{n-1}x^{(i)}\]e centralizamos cada observação:
\[y^{(i)}=x^{(i)}-\mu.\]O vetor $y^{(i)}$ informa quanto a observação $i$ se afasta da média em cada coordenada.
Centralizar move o centro da nuvem para a origem sem alterar sua forma.
Sem esse passo, uma direção poderia parecer importante apenas porque os dados estão distantes do zero, não porque variam muito ao longo da direção.
Vamos empilhar os vetores centralizados nas linhas de $X\in\mathbb{R}^{n\times D}$. Usaremos a matriz de covariância
\[\Sigma=\frac{1}{n}X^\top X\in\mathbb{R}^{D\times D}.\]O elemento $\Sigma_{jk}$ mede como as coordenadas $j$ e $k$ variam juntas, com os dois índices percorrendo $0,1,\ldots,D-1$. Na diagonal, $\Sigma_{jj}$ é a variância da coordenada $j$. Escolhemos o denominador $n$ porque estamos medindo a perda média no próprio conjunto de treino; usar $n-1$ multiplicaria todos os autovalores pelo mesmo fator e não mudaria as direções principais.
Agora tome uma direção unitária $u\in\mathbb{R}^D$, para a qual $\lVert u\rVert_2=1$. O número
\[t_i=u^\top y^{(i)}\]é o escore da observação $i$ ao longo de $u$: uma coordenada escalar depois de projetarmos a observação nessa direção. Como os dados estão centrados, a variância desses escores será data por
\[\frac{1}{n}\sum_{i=0}^{n-1}t_i^2 =u^\top\Sigma u.\]A primeira direção principal $v_0$ é aquela que maximiza essa variância entre todas as direções unitárias, e será dada por:
\[v_0 =\underset{\lVert u\rVert_2=1}{\operatorname{arg\,max}}\, u^\top\Sigma u.\]Como $\Sigma$ é simétrica e semidefinida positiva, ela possui autovetores ortonormais $v_0,v_1,\ldots,v_{D-1}$, com autovalores $\lambda_0\ge\lambda_1\ge\cdots\ge\lambda_{D-1}\ge0$. O escalar $\lambda_j$ mede a variância ao longo da direção $v_j$. Se escrevermos qualquer direção unitária como $u=\sum_j a_jv_j$, o coeficiente $a_j$ mede quanto de $v_j$ participa de $u$. A condição de norma unitária implica $\sum_j a_j^2=1$ e
\[u^\top\Sigma u =\sum_{j=0}^{D-1}\lambda_j a_j^2 \le\lambda_0\sum_{j=0}^{D-1}a_j^2 =\lambda_0.\]Logo, $v_0$ vence porque seu autovalor $\lambda_0$ é a maior variância que uma projeção unidimensional pode preservar. As direções seguintes repetem a escolha sob a obrigação de permanecer ortogonais às anteriores. Os escores $t_{ij}=v_j^\top y^{(i)}$ são as coordenadas chamadas componentes principais. Assim, a PCA não seleciona simplesmente algumas variáveis originais: ela gira o sistema de coordenadas para os eixos $v_j$ e, quando reduzimos a dimensão, descarta os eixos de menor variância.
Se conservarmos as primeiras $d$ direções, a fração de variância explicada será
\[\rho_d =\frac{\sum_{j=0}^{d-1}\lambda_j} {\sum_{j=0}^{D-1}\lambda_j}.\]O número $\rho_d$ mede a variação total preservada pelo subespaço, não a utilidade para uma tarefa futura. A PCA também é sensível às unidades: centralizar remove a média, mas não iguala escalas. Uma coordenada medida em milhares pode dominar outra medida entre zero e um. Padronizar ou não padronizar precisa, portanto, ser uma decisão do problema, nunca um reflexo automático.
Vamos antecipar os oito pontos que reutilizaremos na Seção 3. Eles possuem $x\in{-1,1}$ e $y\in{-3,-1,1,3}$, com todas as combinações. A média é $(0,0)$ e a covariância é $\Sigma=\operatorname{diag}(1,5)$. Portanto, $v_0=(0,1)$ preserva variância $\lambda_0=5$, enquanto $v_1=(1,0)$ responde pela variância $\lambda_1=1$. Uma única componente conserva $5/(5+1)=83{,}33\%$ da variância. A Figura 1 torna visível o preço dessa escolha: os pares $(-1,y)$ e $(1,y)$ recebem o mesmo escore $t_i=y$ e a mesma reconstrução $(0,y)$.
Figura 1: Ao conservar apenas $v_0=(0,1)$, a PCA preserva $5/6=83{,}33\%$ da variância e transforma cada par $(-1,y)$ e $(1,y)$ na mesma reconstrução $(0,y)$.
2.2 Por que o autoencoder linear encontra o mesmo subespaço
Vamos voltar ao gargalo introduzido na Seção 1. O número $D$ conta as coordenadas da entrada e $d$ conta as coordenadas da representação latente; impor $d\lt D$ obriga o encoder a descrever cada observação com menos números do que recebeu. No caso linear unidimensional que estudaremos agora, $d=1$: cada observação já centralizada $x\in\mathbb{R}^D$ será reduzida ao escalar $z$, e o decoder terá apenas esse número para reconstruir as $D$ coordenadas da entrada. Uma direção unitária $u\in\mathbb{R}^D$, cuja norma satisfaz $\lVert u\rVert_2=1$, definirá qual eixo da variação observada atravessará esse gargalo.
O encoder unidimensional calculará
\[z=u^\top x,\]e o decoder reconstruirá
\[\hat{x}=uz=uu^\top x.\]A matriz $uu^\top$ projeta $x$ sobre a reta gerada por $u$. Como $u^\top u=1$, a norma quadrática do resíduo satisfaz
\[\begin{aligned} \lVert x-uu^\top x\rVert_2^2 &=x^\top x-2(u^\top x)^2+(u^\top x)^2(u^\top u)\\ &=\lVert x\rVert_2^2-(u^\top x)^2. \end{aligned}\]O primeiro termo não depende de $u$. Portanto, minimizar a soma dos erros de reconstrução equivale a maximizar $\sum_i(u^\top y^{(i)})^2$, exatamente a variância projetada que definiu $v_0$ na subseção anterior. E aqui temos uma conclusão importante:
O melhor autoencoder linear unidimensional e a primeira direção da PCA resolvem o mesmo problema geométrico.
Para um gargalo com $d$ dimensões, seja $U=[\,v_0\;v_1\;\cdots\;v_{d-1}\,]\in\mathbb{R}^{D\times d}$ a matriz que reúne as $d$ direções principais preservadas. Suas colunas são ortonormais, portanto $U^\top U=I_d$. A escolha $W_e=U^\top$ para o encoder e $W_d=U$ para o decoder produz a projeção $UU^\top x$.
Essa equivalência tem fronteiras. Ela exige transformações lineares, dados centrados, gargalo subcompleto e perda quadrática. Um autoencoder não linear não é uma PCA com mais camadas: sua reconstrução pode seguir uma variedade curva, e sua otimização já não se reduz aos autovetores de uma covariância. Mesmo no caso linear, a perda identifica primeiro o subespaço e não um significado único para cada coordenada latente.
2.3 A derivação matricial e o erro que sobra
Retome a matriz centrada $X\in\mathbb{R}^{n\times D}$ da Subseção 2.1. O código e a reconstrução de todo o conjunto são
\[Z=XU,\qquad \hat{X}=XUU^\top.\]O produto $P=UU^\top$ é um projetor ortogonal. Ele é simétrico, pois $P^\top=UU^\top=P$, e idempotente, pois
\[P^2=UU^\top UU^\top=U I_d U^\top=P.\]Recuperação: norma de Frobenius
A norma de Frobenius estende aos elementos de uma matriz a mesma soma quadrática usada na norma euclidiana de um vetor. Considere a matriz de resíduos $E=X-\hat{X}\in\mathbb{R}^{n\times D}$, na qual as $n$ linhas correspondem às observações, as $D$ colunas correspondem às coordenadas de cada observação e $E_{ij}$ é o erro de reconstrução da coordenada $j$ na observação $i$. A norma de Frobenius de $E$ é
\[\lVert E\rVert_F =\sqrt{\sum_{i=0}^{n-1}\sum_{j=0}^{D-1}E_{ij}^2}.\]O subscrito $F$ identifica Frobenius; não é um expoente. A norma eleva cada erro ao quadrado, soma os $nD$ resultados e extrai a raiz. Por isso, $\lVert E\rVert_F^2$ é o erro quadrático total do conjunto: dividi-lo por $n$ produz o erro quadrático médio por observação, enquanto dividi-lo por $nD$ produz o MSE por coordenada.
Com essa definição, a perda quadrática total pode ser escrita de forma compacta como
\[\lVert X-\hat{X}\rVert_F^2 =\lVert X(I-P)\rVert_F^2.\]Escolher em $U$ os autovetores associados aos $d$ maiores autovalores de $\Sigma=X^\top X/n$ minimiza essa perda. A variância média preservada é $\sum_{j=0}^{d-1}\lambda_j$. A variância média descartada, que também é o erro quadrático médio por observação, vale
\[\frac{1}{n}\lVert X-\hat{X}\rVert_F^2 =\sum_{j=d}^{D-1}\lambda_j.\]Não confundamos essa quantidade com o MSE por coordenada usado no restante do artigo. Cada observação possui $D$ coordenadas; por isso, o MSE por coordenada é
\[\frac{1}{nD}\lVert X-\hat{X}\rVert_F^2 =\frac{1}{D}\sum_{j=d}^{D-1}\lambda_j.\]Nos dados da Figura 1, o espectro é $(5,1)$ e conservamos $d=1$ dimensão. O melhor projetor guarda variância $5$, perde variância $1$ por observação e produz MSE $1/2=0{,}5$ por coordenada. Nenhuma direção inclinada pode perder menos, porque transferir peso de $v_0$ para $v_1$ troca uma direção de variância maior por outra menor.
A liberdade interna do latente também fica visível. Para qualquer matriz ortogonal $R\in\mathbb{R}^{d\times d}$,
\[(UR)(UR)^\top=URR^\top U^\top=UU^\top.\]Os códigos $XU$ e $XUR$ têm coordenadas diferentes, mas produzem o mesmo projetor e a mesma reconstrução. Com pesos não amarrados, a indeterminação é ainda maior. Para qualquer matriz invertível $A\in\mathbb{R}^{d\times d}$, podemos escolher
\[W_e=AU^\top, \qquad W_d=UA^{-1},\]e obter $W_dW_e=UU^\top$. Interpretar uma coordenada latente isolada como conceito exige evidência adicional; a perda identifica o subespaço principal, mas não batiza seus eixos.
Chegamos ao resultado de que precisamos para entender o gargalo.
A PCA fornece uma redução linear ordenada pela variância, quantifica quanto cada direção conserva e entrega a melhor reconstrução quadrática dentro dessa família. Ela não conhece classes, significado ou causalidade. Essa ausência será justamente o problema da próxima seção.
2.4 Exercícios de lápis e papel
1. Verifique por multiplicação direta que $u=(3/5,4/5)$ possui norma unitária e que $uu^\top$ é idempotente.
Solução: A norma quadrática de $u$ é
\[\lVert u\rVert_2^2 =\left(\frac35\right)^2+\left(\frac45\right)^2 =\frac9{25}+\frac{16}{25} =1.\]Logo, $u$ possui norma unitária. O produto externo é
\[P=uu^\top =\begin{bmatrix} 9/25&12/25\\ 12/25&16/25 \end{bmatrix}.\]Multiplicando $P$ por ele mesmo,
\[P^2 =\frac{1}{625} \begin{bmatrix} 9&12\\12&16 \end{bmatrix} \begin{bmatrix} 9&12\\12&16 \end{bmatrix} =\frac{1}{625} \begin{bmatrix} 225&300\\300&400 \end{bmatrix} =P.\]Portanto, $uu^\top$ é idempotente.
2. Projete $x=(2,1)$ sobre $u=(1,0)$ e calcule o erro quadrático descartado.
Solução: O código escalar é
\[z=u^\top x=1\cdot2+0\cdot1=2.\]A projeção vale
\[\hat{x}=uz=(1,0)2=(2,0).\]O resíduo descartado é $x-\hat{x}=(0,1)$, portanto seu erro quadrático é
\[\lVert x-\hat{x}\rVert_2^2=0^2+1^2=1.\]3. Para autovalores $(9,4,1)$, calcule a variância preservada, a descartada e a fração explicada com gargalos de dimensões $1$ e $2$.
Solução: Com $d=1$, preservamos apenas o maior autovalor:
\[V_{\mathrm{preservada}}=9, \qquad V_{\mathrm{descartada}}=4+1=5, \qquad \rho_1=\frac{9}{9+4+1}=\frac{9}{14}\approx64{,}29\%.\]Com $d=2$, preservamos os dois maiores:
\[V_{\mathrm{preservada}}=9+4=13, \qquad V_{\mathrm{descartada}}=1, \qquad \rho_2=\frac{13}{14}\approx92{,}86\%.\]A segunda componente acrescenta $4/14\approx28{,}57$ pontos percentuais de variância explicada.
4. Use $U=I_2$ e a rotação de $90^\circ$
\[R=\begin{bmatrix}0&-1\\1&0\end{bmatrix}\]para verificar que $(UR)(UR)^\top=UU^\top$
Solução: Como $U=I_2$, temos $UR=R$. Assim,
\[(UR)(UR)^\top =RR^\top =\begin{bmatrix}0&-1\\1&0\end{bmatrix} \begin{bmatrix}0&1\\-1&0\end{bmatrix} =\begin{bmatrix}1&0\\0&1\end{bmatrix} =I_2.\]Por outro lado,
\[UU^\top=I_2I_2^\top=I_2.\]As coordenadas latentes foram rotacionadas, mas o projetor permaneceu o mesmo.
5. Os pontos $(2,0)$, $(-2,0)$, $(0,1)$ e $(0,-1)$ têm média zero. Calcule as variâncias nos eixos e determine a direção escolhida por um autoencoder linear unidimensional.
Solução: Como as médias são nulas, as variâncias são as médias dos quadrados. No eixo $x$,
\[\operatorname{Var}(x) =\frac{2^2+(-2)^2+0^2+0^2}{4} =2.\]No eixo $y$,
\[\operatorname{Var}(y) =\frac{0^2+0^2+1^2+(-1)^2}{4} =0{,}5.\]Como $2>0{,}5$, o autoencoder linear unidimensional escolhe o eixo $x$, a direção de maior variância.
3. Reconstruir exige escolher uma distância
Até aqui, partimos da função identidade, impusemos um gargalo e resolvemos o caso linear. Vimos que, com dados centrados, $d\lt D$ e perda quadrática, o melhor autoencoder conserva o mesmo subespaço escolhido pela PCA: aquele que retém as direções de maior variância. Agora precisamos examinar a régua que tornou essa solução ótima.
Essa régua foi o MSE, definido na Seção 1. Para uma entrada $x\in\mathbb{R}^D$ e sua reconstrução $\hat{x}$, ele eleva ao quadrado a diferença em cada uma das $D$ coordenadas, soma essas dívidas e divide o resultado por $D$. Assim, mede proximidade coordenada a coordenada e faz erros grandes pesarem mais, mas não sabe quais diferenças afetarão uma tarefa posterior.
Em imagens, deslocar um objeto por um pixel altera muitas coordenadas embora preserve o objeto percebido. Textura, reflexo, iluminação e ruído de sensor também entram na conta. Se essas variações dominam o erro, a representação recebe incentivo para conservá-las.
Isso não torna a reconstrução ruim. Torna sua promessa precisa: boa reconstrução significa proximidade segundo $\mathcal{D}(x,\hat{x})$. Utilidade para classificação, controle ou busca é outra propriedade e precisa ser medida separadamente.
Vamos agora atribuir classes aos oito pontos da Figura 1. Eles continuam distribuídos nas colunas $x=-1$ e $x=1$, com alturas $y\in{-3,-1,1,3}$, mas a classe passa a ser determinada apenas pelo sinal de $x$. Como calculamos na Seção 2, os dados possuem
\[\operatorname{Var}(x)=1,\qquad \operatorname{Var}(y)=5,\qquad \operatorname{Cov}(x,y)=0.\]A conta cabe na margem. Como a média das duas coordenadas é zero, $\operatorname{Var}(x)=8/8=1$ e $\operatorname{Var}(y)=2(9+1+1+9)/8=5$. A covariância também é zero, pois os produtos $x_i y_i$ se cancelam dois a dois.
A PCA prefere o eixo $y$, pois nele está a maior variância. Projetar sobre $y$ produz MSE de $0{,}5$ por coordenada, mas a classificação pelo sinal do latente cai para $50\%$. Projetar sobre $x$ eleva o MSE para $2{,}5$, porém separa as classes com $100\%$ de acurácia. Ao escolher a melhor reconstrução, perdemos exatamente a coordenada que define a classe.
Nenhum rótulo participa do treino do autoencoder. Nós o usamos apenas depois, como instrumento de diagnóstico. A função objetivo não conhece nossa preferência sem que a coloquemos na perda, nos dados ou na arquitetura. A representação pode reconstruir melhor e servir pior.
3.1 A distância define um estimador
O MSE possui uma consequência probabilística precisa. Se várias entradas originais $x$ forem compatíveis com a mesma informação disponível ao decoder, a saída que minimiza o erro quadrático esperado é a média condicional. Já a perda absoluta,
\[\mathcal{L}_1(a)=\mathbb{E}[\,|x-a|\,],\]é minimizada por uma mediana condicional. A função de perda muda, portanto, não apenas o tamanho do erro, mas o resumo estatístico que a rede deve produzir.
Considere uma variável escalar que vale $-2$ com probabilidade $1/2$ e $2$ com probabilidade $1/2$. Sob MSE, a melhor previsão única é $a=0$:
\[\frac12(-2-a)^2+\frac12(2-a)^2=4+a^2.\]O valor mínimo é $4$. A saída média, zero, nunca ocorre nos dados. Esse é o mecanismo por trás de reconstruções borradas quando muitos detalhes plausíveis competem pela mesma região: a média paga menos erro quadrático do que escolher arbitrariamente um modo, embora possa parecer menos real.
Podemos também ponderar coordenadas. Com pesos $w_j>0$,
\[\mathcal{L}_{w} =\frac{\sum_{j=0}^{D-1}w_j(x_j-\hat{x}_j)^2} {\sum_{j=0}^{D-1}w_j},\]uma coordenada com peso dez recebe dez vezes a pressão de gradiente de outra com o mesmo erro. Escolher uma perda perceptual transfere essa ideia para features de uma rede. Nada garante neutralidade: toda métrica embute uma definição operacional de semelhança.
3.2 Laboratório: reconstrução e utilidade disputam a reta
No laboratório, os pontos azuis e âmbar formam as duas classes; os pontos verdes são as reconstruções. Comece em $0^\circ$: a representação conserva a classe, mas descarta toda a variação vertical. Depois gire o gargalo até $90^\circ$. O MSE cai de $2{,}5$ para $0{,}5$, uma redução de $80\%$, enquanto a acurácia cai de $100\%$ para $50\%$. A maior variância venceu, exatamente como a Seção 2 previu. O controle de ruído permite observar quanto essa disputa muda quando corrompemos a entrada.
3.3 Exercícios de lápis e papel
1. Calcule o MSE das previsões $a=-2$, $a=0$ e $a=2$ para a distribuição simétrica da Seção 3.1.
Solução: Para uma variável que assume $-2$ e $2$ com probabilidades iguais, o MSE de uma previsão $a$ é
\[\frac12(-2-a)^2+\frac12(2-a)^2=4+a^2.\]Portanto,
\[\operatorname{MSE}(-2)=8, \qquad \operatorname{MSE}(0)=4, \qquad \operatorname{MSE}(2)=8\]A média $a=0$ minimiza o erro quadrático.
2. Para valores ${0,0,0,8}$ equiprováveis, calcule a média, uma mediana e compare qual delas minimiza MSE e erro absoluto.
Solução: A média é
\[\bar{x}=\frac{0+0+0+8}{4}=2.\]Os dois valores centrais da amostra ordenada são $0$ e $0$, portanto uma mediana é $0$. Para o MSE,
\[\operatorname{MSE}(2) =\frac{3(0-2)^2+(8-2)^2}{4} =\frac{12+36}{4} =12,\]enquanto
\[\operatorname{MSE}(0)=\frac{0+0+0+8^2}{4}=16.\]Para o erro absoluto médio,
\[\operatorname{MAE}(0)=\frac{8}{4}=2\]e
\[\operatorname{MAE}(2)=\frac{2+2+2+6}{4}=3.\]Assim, a média $2$ minimiza o MSE, enquanto a mediana $0$ minimiza o erro absoluto.
3. Com $x=(1,4)$, $\hat{x}=(3,3)$ e pesos $(1,4)$, calcule $\mathcal{L}_w$.
Solução: Os erros quadráticos são $(1-3)^2=4$ e $(4-3)^2=1$. Aplicando os pesos,
\[\mathcal{L}_w =\frac{1\cdot4+4\cdot1}{1+4} =\frac85 =1{,}6.\]4. Nos oito pontos da Seção 3, some os erros ao projetar sobre o eixo $x$ e confirme o MSE por coordenada de $2{,}5$.
Solução: A projeção sobre o eixo $x$ preserva a primeira coordenada e substitui $y$ por zero. Em cada coluna, os erros quadráticos verticais somam
\[(-3)^2+(-1)^2+1^2+3^2=9+1+1+9=20.\]Como há duas colunas, a soma total é $40$. Os oito pontos possuem dezesseis coordenadas, logo
\[\operatorname{MSE}=\frac{40}{16}=2{,}5.\]5. Explique, em duas frases, por que uma placa pequena pode contribuir pouco para o MSE de uma imagem e ainda decidir a ação correta de um veículo.
Solução: Como a placa ocupa poucos pixels, seus erros contribuem pouco para a média calculada sobre todas as coordenadas da imagem. Apesar disso, seu conteúdo pode distinguir entre parar e prosseguir, de modo que descartá-lo altera diretamente a ação segura.
4. Denoising muda a tarefa
Um denoising autoencoder (autocodificador de remoção de ruído) recebe uma versão corrompida $\tilde{x}$, amostrada de uma distribuição de corrupção $q(\tilde{x}\mid x)$, mas continua obrigado a reconstruir o original:
\[z=f_\theta(\tilde{x}),\qquad \hat{x}=g_\phi(z),\qquad \mathcal{L}=\mathcal{D}(x,\hat{x}).\]Agora copiar a observação não basta, mesmo com uma representação larga, pois $\tilde{x}\ne x$. Para remover a corrupção, o modelo precisa explorar regularidades que se repetem nos dados. Sob MSE e capacidade suficiente, a previsão ótima é a média condicional $\mathbb{E}[x\mid\tilde{x}]$. Para uma saída candidata $a$, a decomposição
\[\mathbb{E}\!\left[\lVert x-a\rVert_2^2\mid\tilde{x}\right] = \mathbb{E}\!\left[ \left\lVert x-\mathbb{E}[x\mid\tilde{x}]\right\rVert_2^2 \mid\tilde{x}\right] + \left\lVert a-\mathbb{E}[x\mid\tilde{x}]\right\rVert_2^2\]separa um termo independente de $a$ e outro não negativo. O segundo zera somente quando $a=\mathbb{E}[x\mid\tilde{x}]$. Entre todos os originais compatíveis com a entrada corrompida, a rede devolve sua média.
A corrupção, portanto, não é um ornamento estatístico. Ruído gaussiano, remoção de coordenadas e mascaramento definem problemas diferentes e favorecem regularidades diferentes. Essa decisão atravessará a série. O SimCLR construirá duas views por aumentos; o MAE esconderá patches; o I-JEPA esconderá regiões e preverá representações. Em todos esses métodos, escolher o que falta é escolher parte do que será aprendido.
4.1 Do denoising ao gradiente da densidade
Vincent e colaboradores introduziram o denoising autoencoder em 2008 como forma de aprender features robustas a corrupção. Alain e Bengio mostraram depois uma ligação mais profunda. Seja $p(x)$ a densidade dos dados e seja a corrupção gaussiana $\tilde{x}=x+\varepsilon$, com $\varepsilon\sim N(0,\sigma^2I)$. Para ruído pequeno e um reconstrutor ótimo $r_\sigma$, vale assintoticamente
\[r_\sigma(x)-x =\sigma^2\nabla_x\log p(x)+o(\sigma^2).\]O vetor $\nabla_x\log p(x)$ é o score da distribuição. Ele aponta na direção em que a log-densidade cresce mais rapidamente. Assim, o deslocamento produzido pela reconstrução, dividido por $\sigma^2$, estima localmente uma direção que leva um ponto ruidoso para regiões mais prováveis dos dados.
Num exemplo unidimensional gaussiano, $p(x)=N(0,s^2)$, temos
\[\frac{d}{dx}\log p(x)=-\frac{x}{s^2}.\]Para $\sigma$ pequeno, o reconstrutor desloca $x$ aproximadamente por $-\sigma^2x/s^2$, isto é, puxa valores para a região de maior densidade em torno de zero. Esta ligação entre remoção de ruído e score reaparecerá no próximo artigo. Modelos de difusão organizam muitos níveis de corrupção e aprendem precisamente como reverter essa perda progressiva de estrutura.
A aproximação exige cuidado. Ela descreve o reconstrutor ótimo no limite de ruído pequeno e capacidade suficiente; não afirma que qualquer autoencoder treinado por algumas épocas recuperou a densidade verdadeira. Matemática útil deixa explícitas também as suas condições.
4.2 Exercícios de lápis e papel
1. Para $p(x)=N(0,4)$, calcule o score em $x=2$.
Solução: Para uma gaussiana $N(0,s^2)$, o score é $-x/s^2$. Como $s^2=4$,
\[\left.\frac{d}{dx}\log p(x)\right|_{x=2} =-\frac{2}{4} =-0{,}5.\]2. Usando $\sigma=0{,}1$ no exercício anterior, aproxime $r_\sigma(2)-2$ pela fórmula de Alain e Bengio.
Solução: A aproximação fornece
\[r_\sigma(2)-2 \approx\sigma^2\nabla_x\log p(2) =(0{,}1)^2(-0{,}5) =-0{,}005.\]O reconstrutor desloca o valor $2$ ligeiramente em direção à região de maior densidade, próxima de zero.
3. Se $\tilde{x}$ pode vir de $x=0$ ou $x=2$ com probabilidades condicionais iguais, calcule a reconstrução ótima sob MSE.
Solução: Sob MSE, a reconstrução ótima é a média condicional. Como os dois originais têm probabilidade $1/2$,
\[\mathbb{E}[x\mid\tilde{x}] =\frac12\cdot0+\frac12\cdot2 =1.\]4. Uma corrupção zera cada coordenada com probabilidade $0{,}3$. Qual é o número esperado de coordenadas preservadas em um vetor de dimensão $20$?
Solução: Cada coordenada é preservada com probabilidade $1-0{,}3=0{,}7$. Pela linearidade da esperança,
\[\mathbb{E}[N_{\mathrm{preservadas}}] =20\cdot0{,}7 =14.\]5. Compare, em uma frase para cada caso, o que uma corrupção gaussiana pequena e uma máscara de bloco grande obrigam o modelo a inferir.
Solução: A corrupção gaussiana pequena obriga o modelo a corrigir perturbações locais usando a vizinhança estatística do ponto observado. Uma máscara de bloco grande remove uma região inteira e obriga o modelo a inferir estrutura global e conteúdo ausente a partir de um contexto mais distante.
5. Do experimento controlado ao autoencoder treinável em C++23
5.1 Uma reta escolhida à mão
Nas Seções 2 e 3, vimos que uma mesma projeção pode reconstruir bem os dados e, ainda assim, descartar a coordenada que separa as classes. Vamos agora transformar essa disputa em um programa completo em C++23, mantendo visível a geometria que produz cada resultado. Reutilizaremos os oito pontos da Seção 3: cada entrada $x=(x_0,x_1)$ possui duas coordenadas, enquanto o gargalo conserva apenas o escalar $z=u^\top x$. A direção unitária $u=(\cos\theta,\sin\theta)$, determinada pelo ângulo $\theta$, escolhe qual reta atravessará o gargalo; o decoder reconstrói o ponto por $\hat{x}=zu$.
Não treinaremos $u$ com um otimizador porque nosso objetivo, nesta etapa, não é reproduzir o processo de ajuste, mas comparar explicitamente as direções disponíveis ao modelo; por isso, o programa avaliará os ângulos $0^\circ$, $30^\circ$ e $90^\circ$. Para cada um, calculará o MSE médio sobre as dezesseis coordenadas dos oito pontos e usará apenas o sinal de $z$ para prever as classes $-1$ e $1$. Assim, poderemos acompanhar a reta girar do eixo $x$, que preserva a classe, para o eixo $y$, que preserva a maior variância e oferece a melhor reconstrução.
Mantendo os dados, a arquitetura e a dimensão latente fixos, isolamos o efeito da direção $u$. A saída do programa tornará concreta a tensão que investigamos: reduzir o MSE não garante que a representação conserve a informação necessária para a tarefa posterior.
#include <array>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <numbers>
#include <span>
struct Point {
double x{};
double y{};
int label{};
};
struct Result {
double latent{};
Point reconstruction{};
};
Result encode_decode(const Point& point, const double angle) {
const double ux = std::cos(angle);
const double uy = std::sin(angle);
const double latent = ux * point.x + uy * point.y;
return {latent, {latent * ux, latent * uy, point.label}};
}
double reconstruction_mse(
const std::span<const Point> points,
const double angle) {
double squared_error = 0.0;
for (const auto& point : points) {
const auto result = encode_decode(point, angle);
const double error_x = point.x - result.reconstruction.x;
const double error_y = point.y - result.reconstruction.y;
squared_error += error_x * error_x + error_y * error_y;
}
const double coordinate_count =
2.0 * static_cast<double>(points.size());
return squared_error / coordinate_count;
}
int main() {
const std::array points{
Point{-1.0, -3.0, -1}, Point{-1.0, -1.0, -1},
Point{-1.0, 1.0, -1}, Point{-1.0, 3.0, -1},
Point{1.0, -3.0, 1}, Point{1.0, -1.0, 1},
Point{1.0, 1.0, 1}, Point{1.0, 3.0, 1}};
for (const double degrees : {0.0, 30.0, 90.0}) {
const double angle = degrees * std::numbers::pi / 180.0;
std::size_t correct = 0;
for (const auto& point : points) {
const double latent = encode_decode(point, angle).latent;
const int prediction = latent >= 0.0 ? 1 : -1;
correct += prediction == point.label;
}
std::cout << std::fixed << std::setprecision(3)
<< "ângulo=" << degrees
<< " mse=" << reconstruction_mse(points, angle)
<< " acurácia=" << 100.0 * correct / points.size()
<< "%\n";
}
}
Compilado em C++23, o programa produz:
ângulo=0.000 mse=2.500 acurácia=100.000%
ângulo=30.000 mse=2.000 acurácia=75.000%
ângulo=90.000 mse=0.500 acurácia=50.000%
O código usa apenas a biblioteca padrão e funciona como referência de correção, não como implementação otimizada. Ele nos deu algo mais valioso do que velocidade: uma situação em que sabemos, antes do treino, qual reta minimiza a reconstrução e qual reta preserva a classe. Agora podemos retirar nossa mão do ângulo, entregar os pesos ao gradiente e verificar se a teoria sobrevive a dados que não cabem numa folha de papel.
5.2 Exercícios de rastreamento manual
1. Execute encode_decode à mão para o ponto $(1,3)$ e ângulo $0^\circ$.
Solução: Para $0^\circ$, temos $u=(\cos0^\circ,\sin0^\circ)=(1,0)$. O latente é
\[z=1\cdot1+0\cdot3=1.\]A reconstrução é
\[\hat{x}=zu=(1,0).\]2. Repita para o mesmo ponto e ângulo $90^\circ$.
Solução: Para $90^\circ$, temos $u=(0,1)$. Portanto,
\[z=0\cdot1+1\cdot3=3\]e
\[\hat{x}=zu=(0,3).\]3. Para o ponto $(-1,1)$ e ângulo $30^\circ$, use $\cos30^\circ=\sqrt{3}/2$ e $\sin30^\circ=1/2$ para calcular o latente.
Solução: Substituindo as coordenadas,
\[z =\frac{\sqrt3}{2}(-1)+\frac12(1) =\frac{1-\sqrt3}{2} \approx-0{,}366.\]4. Explique por que dividir squared_error por 2 * points.size() produz MSE por coordenada, e não por ponto.
Solução: Para cada ponto, squared_error soma um erro da coordenada $x$ e outro da coordenada $y$. Com $n$ pontos, existem $2n$ erros coordenada a coordenada. Dividir por 2 * points.size() calcula, portanto, a média sobre as $2n$ coordenadas; dividir apenas por points.size() produziria a soma média dos dois erros de cada ponto.
5. Se a condição de classificação mudasse de latent >= 0.0 para latent > 0.0, identifique quais casos poderiam mudar e por quê.
Solução: Apenas exemplos com latent == 0.0 poderiam mudar. A condição original atribui classe $1$ a esses exemplos, enquanto a nova condição lhes atribuiria classe $-1$. Para os oito pontos e os ângulos $0^\circ$, $30^\circ$ e $90^\circ$ usados pelo programa, nenhum latente é exatamente zero, portanto a saída exibida permaneceria igual.
5.3 O que o experimento real precisa demonstrar
Nos oito pontos anteriores, a entrada tinha $D=2$ coordenadas, o latente tinha $d=1$ e nós mesmos escolhemos três candidatos para a direção $u$. O experimento real fará três mudanças. Cada entrada passará a ser uma imagem com $D=28^2=784$ pixels; o gargalo conservará $d=16$ valores; e uma matriz $W$ será aprendida por gradiente, em vez de ser escolhida por inspeção.
A pergunta também precisa ficar mais concreta. Um autoencoder estritamente linear consegue aprender, sem receber classes, um subespaço de 16 dimensões que reconstrua roupas descritas por 784 pixels? A execução deverá mostrar três evidências: o MSE precisa cair em exemplos de treino, precisa cair também na validação e no teste que não atualizam os pesos, e as reconstruções precisam conservar formas reconhecíveis. O primeiro resultado confirma que o algoritmo ajusta $W$; o segundo impede que confundamos memorização do subconjunto com generalização; o terceiro mostra o que um único número agregado, o MSE, não consegue contar.
Continuaremos deliberadamente no caso linear. Uma camada não linear poderia produzir imagens melhores, mas misturaria o teste da teoria da Seção 2 com uma arquitetura nova. Aqui, o objetivo é observar a versão treinável da mesma projeção que conduz à PCA. O programa completo está em autoencoder_fashion_mnist.cpp; os trechos a seguir constroem seu contrato na ordem em que os dados atravessam a execução.
5.4 Fashion-MNIST: origem, espelho e partições
Usaremos o Fashion-MNIST, criado por Han Xiao, Kashif Rasul e Roland Vollgraf na Zalando Research. O conjunto contém $60\,000$ imagens de treino e $10\,000$ imagens de teste. Cada exemplo é uma imagem em tons de cinza de $28\times28$ pixels, acompanhada por uma classe entre 0 e 9: camiseta, calça, pulôver, vestido, casaco, sandália, camisa, tênis, bolsa ou bota. O repositório oficial distribui o projeto sob licença MIT e fornece quatro arquivos IDX comprimidos com gzip.
Esses arquivos oficiais seriam a escolha natural se já tivéssemos um descompressor. Não temos, e isso importa: a biblioteca padrão do C++23 não oferece gzip. Implementar o formato DEFLATE dentro de uma seção sobre autoencoders deslocaria o assunto; incorporar zlib acrescentaria justamente uma biblioteca que a leitora precisaria instalar. Por isso, faremos o download do espelho público de versão 1 mantido pelo OpenML, no qual as mesmas $70\,000$ observações aparecem descomprimidas em um único arquivo ARFF. O preço da transparência é baixar 155.225.241 bytes, cerca de 148 MiB, em vez dos aproximadamente 30 MiB dos arquivos comprimidos.
O registro do OpenML informa o identificador 40996, o arquivo Fashion-MNIST.arff, a URL de aquisição e o MD5 cdfc9c58cb9fe86ffaa76af247ae2ef2. A inspeção do arquivo confirma 785 atributos e $70\,000$ linhas de dados. As primeiras $60\,000$ linhas preservam a partição oficial de treino e as últimas $10\,000$, a de teste. Dentro da primeira partição, o modo didático sorteia, com semente fixa, $10\,000$ índices para treino e os próximos $2\,000$ para validação; sorteia ainda $2\,000$ exemplos da partição oficial de teste. --full usa $55\,000$ exemplos de treino, $5\,000$ de validação e todos os $10\,000$ de teste. Em nenhum modo um exemplo de validação ou teste participa do cálculo da média ou da atualização de $W$.
O arquivo contém os rótulos, mas o autoencoder não os recebe. Eles são lidos como metadados e consultados somente depois do treino, para identificarmos as oito imagens da folha de reconstruções. Se a classe entrasse no vetor de entrada, na perda, no embaralhamento ou na escolha dos pesos, já não estaríamos testando aprendizado não supervisionado.
5.5 Fazer download em C++23 exige dizer o que não é C++23
C++23 não possui uma API padrão para HTTP ou TLS. Neste programa voltado ao Windows, a fronteira de aquisição usa URLDownloadToFileW, fornecida pelo sistema operacional em Urlmon.lib; o cálculo do MD5 usa a API criptográfica do Windows em Bcrypt.lib. O restante do leitor, do modelo, do treino e da exportação usa somente a biblioteca padrão. Portanto, a frase correta não é “a biblioteca padrão baixa o conjunto”, mas “um programa C++23 chama duas APIs do Windows para adquirir e verificar o arquivo”.
O caminho feliz não basta. ensure_dataset primeiro verifica tamanho e MD5 de uma cópia existente. Se ambos correspondem ao registro do OpenML, não há novo download. Caso contrário, a função baixa para um nome terminado em .part, verifica essa cópia e somente então substitui o destino:
constexpr std::uintmax_t expected_file_size = 155'225'241;
constexpr std::string_view expected_md5 =
"cdfc9c58cb9fe86ffaa76af247ae2ef2";
const HRESULT result = URLDownloadToFileW(
nullptr, dataset_url, temporary.c_str(), 0, nullptr);
if (FAILED(result)) {
fail("URLDownloadToFileW não concluiu o download");
}
if (!has_expected_fingerprint(temporary)) {
fail("tamanho ou MD5 não corresponde ao registro do OpenML");
}
Esse protocolo torna a aquisição idempotente e evita que uma conexão interrompida seja confundida com dados válidos. A extensão .part também impede o leitor de abrir um arquivo ainda em transferência. O programa não confia apenas no valor de retorno da API: ele abre o resultado, confirma os 155.225.241 bytes e calcula os 16 bytes do MD5 em blocos reutilizáveis de 1 MiB. MD5 não serve hoje para proteger senhas ou assinaturas, mas o usamos para a finalidade declarada pelo provedor do conjunto: detectar corrupção acidental e identificar exatamente o arquivo publicado.
5.6 Ler o ARFF sem um dataframe
ARFF é um formato textual. Seu cabeçalho declara uma relação, 784 atributos pixel1 a pixel784, um atributo class e, depois de @DATA, uma observação por linha. Cada observação contém 785 inteiros separados por vírgula: 784 intensidades no intervalo $[0,255]$ e o rótulo no intervalo $[0,9]$.
O leitor não precisa de uma biblioteca de CSV nem de um dataframe. Ele percorre o arquivo uma vez com std::getline, conta as declarações @ATTRIBUTE e interpreta os dígitos diretamente na linha. Antes de aceitar o conjunto, exige a relação, a marca @DATA, exatamente 785 atributos, exatamente $70\,000$ exemplos, 785 campos em cada exemplo e todos os valores dentro dos intervalos esperados. Os autotestes entregam ao mesmo leitor um arquivo válido, um arquivo truncado, um pixel igual a 256 e um cabeçalho com dimensão errada; somente o primeiro pode passar.
O núcleo de parse_data_row evita criar 785 substrings e chamar uma conversão genérica para cada uma. Enquanto o caractere for um dígito, o próximo valor decimal é formado em unsigned; em seguida, o leitor confere o limite e exige a vírgula esperada:
unsigned value = 0;
while (position < row.size() && row[position] >= '0' &&
row[position] <= '9') {
value = value * 10U + static_cast<unsigned>(row[position] - '0');
++position;
}
const unsigned maximum = field == dimensions ? 9U : 255U;
if (value > maximum) {
fail("valor fora do intervalo permitido no ARFF");
}
Uma decisão de representação evita desperdiçar memória antes mesmo de falarmos em multiplicações. O arquivo já quantiza cada pixel em oito bits, portanto Dataset conserva os dados brutos em std::uint8_t:
struct Dataset {
std::size_t dimensions{};
std::vector<std::uint8_t> pixels;
std::vector<std::uint8_t> labels;
std::span<const std::uint8_t> image(std::size_t index) const {
return {pixels.data() + index * dimensions, dimensions};
}
};
As $70\,000\times784$ intensidades ocupam 54.880.000 bytes, ou 52,34 MiB. Guardá-las antecipadamente em FP32 consumiria 209,35 MiB sem acrescentar informação. A conversão para float ocorre somente quando uma imagem entra no cálculo. pixels é um bloco contíguo em ordem de exemplos; image(index) devolve uma visão com std::span, sem copiar 784 valores. std::vector aparece aqui como contêiner da biblioteca padrão, não como biblioteca de álgebra vetorial.
5.7 Preparar os pixels sem deixar o treino olhar o teste
Depois de separar índices de treino, validação e teste, o programa calcula uma média $\mu_j$ para cada posição de pixel usando somente os exemplos selecionados do treino. Para a imagem $x$, a entrada numérica do modelo é
\[y_j=\frac{x_j}{255}-\mu_j.\]No código, essa transformação ocorre sobre um único std::span e escreve em um buffer já alocado:
for (std::size_t j = 0; j < image.size(); ++j) {
centered[j] = static_cast<float>(image[j]) / 255.0F - mean[j];
}
Dividir por 255 coloca as intensidades no intervalo $[0,1]$; subtrair a média satisfaz a hipótese de centralização usada na Seção 2. Não dividimos pelo desvio padrão porque pixels das bordas quase sempre pretos podem ter variância muito pequena e receber uma escala desproporcional. Também não redimensionamos, recortamos ou aumentamos as imagens: cada transformação extra afastaria o experimento do problema linear que queremos isolar.
O embaralhamento movimenta apenas índices std::size_t, não imagens. A semente 20260728 fixa as amostras e a ordem inicial; sementes derivadas inicializam $W$ e embaralham os minilotes. Essa separação permite repetir a execução, embora resultados bit a bit ainda possam variar entre compiladores se a ordem das operações de ponto flutuante mudar.
5.8 O modelo, a perda e o gradiente que realmente treinamos
Seja $W\in\mathbb{R}^{d\times D}$ a única matriz treinável. Para uma entrada centralizada $y\in\mathbb{R}^D$, o encoder e o decoder calculam
\[z=Wy, \qquad \hat{y}=W^\top z.\]O decoder reutiliza a transposta do encoder: são pesos ligados, ou tied weights. Com $D=784$ e $d=16$, temos $16\times784=12\,544$ parâmetros FP32, equivalentes a 49 KiB. Duas matrizes independentes teriam $25\,088$ parâmetros. O compartilhamento não é indispensável para um autoencoder linear, mas torna a correspondência com a projeção $UU^\top$ da Seção 2 explícita e obriga cada direção latente a participar dos dois caminhos.
Para um minilote $\mathcal{B}$ com $B$ imagens, otimizamos a metade da soma quadrática média por exemplo,
\[J_{\mathcal{B}}(W) =\frac{1}{2B} \sum_{y\in\mathcal{B}} \left\lVert W^\top Wy-y\right\rVert_2^2.\]O fator $1/2$ elimina um 2 do gradiente. Não dividimos a função de treino por $D$ porque isso apenas multiplicaria o gradiente por uma constante e exigiria compensação na taxa de aprendizado. A métrica apresentada à leitora continua sendo o MSE por pixel:
\[\operatorname{MSE} =\frac{1}{ND} \sum_{i=0}^{N-1} \lVert\hat{y}^{(i)}-y^{(i)}\rVert_2^2.\]Precisamos considerar que $W$ aparece duas vezes, no encoder e no decoder. Para uma imagem, definimos
\[e=W^\top Wy-y, \qquad z=Wy, \qquad q=We.\]Então, a derivada da perda dessa imagem é
\[\nabla_W\frac12\lVert e\rVert_2^2 =ze^\top+qy^\top.\]O primeiro produto externo vem do uso de $W^\top$ no decoder; o segundo, do uso de $W$ no encoder. Ambos têm formato $d\times D$. O autoteste numérico perturba cada peso de um caso $D=3$, $d=2$ por $10^{-3}$ e compara essa expressão com diferenças finitas centrais antes de permitir o treino real.
Depois de calcular projected_error como $q=We$, o acumulador implementa literalmente os dois produtos externos:
for (std::size_t k = 0; k < K; ++k) {
auto gradient_row = std::span{gradient.data() + k * D, D};
const float z = latent[k];
const float q = projected_error[k];
for (std::size_t j = 0; j < D; ++j) {
gradient_row[j] += z * error[j] + q * input[j];
}
}
No minilote, somamos os gradientes, dividimos por $B$ e aplicamos momento clássico:
\[V\leftarrow0{,}9V+\frac1B\sum_{y\in\mathcal{B}}\nabla_WJ_y, \qquad W\leftarrow W-0{,}005V.\]O passo correspondente percorre os três vetores contíguos uma única vez:
const float inverse_batch = 1.0F / static_cast<float>(end - begin);
for (std::size_t i = 0; i < model.weights.size(); ++i) {
work.velocity[i] = config.momentum * work.velocity[i] +
inverse_batch * work.gradient[i];
model.weights[i] -= config.learning_rate * work.velocity[i];
}
As 16 linhas de $W$ começam ortonormais, mas não são congeladas nem reortogonalizadas durante o treino. O gradiente precisa encontrar tanto o subespaço quanto a escala que permite a $W^\top W$ agir como projeção.
5.9 Onde está o trabalho e quais otimizações escolhemos
Não usamos BLAS, Eigen, OpenCV, LibTorch, uma biblioteca de tensores nem uma biblioteca de vetores numéricos. Isso não é uma recomendação para produção. É uma escolha didática: cada produto matriz-vetor e cada parcela do gradiente permanece localizável no código. As únicas dependências fora da biblioteca padrão são Urlmon.lib e Bcrypt.lib, restritas ao download e ao MD5; nenhuma delas participa da álgebra ou do treino.
weights armazena $W$ por linhas, e cada linha contígua contém os 784 pesos de uma direção latente. Essa disposição combina com os três produtos predominantes: $Wy$, $W^\top z$ e $We$. O programa processa uma imagem de cada vez, reaproveita os mesmos buffers para entrada centralizada, erro, latente e erro projetado, e acumula o gradiente do minilote em um bloco contíguo. Ao final do lote, percorre weights, gradient e velocity uma única vez para atualizar os parâmetros.
Os pesos, as ativações e os gradientes usam float; as somas de MSE usam double, porque acumulam milhões de quadrados. O código executa numa única thread e não chama intrínsecos SIMD. Com /O2, o compilador ainda pode vetorizar os laços contíguos. Manter uma linha de base determinística e legível é mais útil aqui do que apresentar uma corrida contra uma BLAS que não medimos. A ordem assintótica de uma época permanece $O(NDd)$; aumentar o modo didático de $d=16$ para o modo completo com $d=32$ aproximadamente duplica o trabalho dominante por imagem.
A configuração padrão usa minilotes de 128 imagens, 12 épocas, 10.000 exemplos de treino, 2.000 de validação e 2.000 de teste. O modo --smoke reduz as três amostras para 512, 128 e 128 exemplos, usa $d=4$ e executa duas épocas; ele ainda baixa e valida o conjunto inteiro, pois seu propósito é testar o encadeamento real. --full usa 55.000 exemplos para ajuste, 5.000 para validação, os 10.000 testes oficiais, $d=32$ e 20 épocas.
5.10 Compilar, executar e olhar o que atravessou o gargalo
Código C++23 completo para abrir e baixar
Os blocos anteriores explicam as partes decisivas; o arquivo abaixo reúne o programa inteiro que faz o download, valida e lê o Fashion-MNIST, executa os autotestes, treina o modelo, calcula o custo físico e grava as métricas e as reconstruções.
No Developer PowerShell for Visual Studio, a configuração verificada neste artigo é compilada com MSVC 19.51 para x64:
cl /nologo /std:c++latest /EHsc /utf-8 /W4 /permissive- /O2 /DNDEBUG `
diffusion\codigo\autoencoder_fashion_mnist.cpp `
/Fe:autoencoder_fashion_mnist.exe
Os #pragma comment(lib, ...) do próprio arquivo ligam Urlmon.lib e Bcrypt.lib. A primeira execução pode ser curta ou didática:
.\autoencoder_fashion_mnist.exe --smoke
.\autoencoder_fashion_mnist.exe
Por padrão, o arquivo verificado fica em dados/Fashion-MNIST.arff. As saídas ficam em resultados_autoencoder/metricas.csv e resultados_autoencoder/reconstrucoes.pgm. CSV conserva o MSE de treino, o MSE de validação e o tempo acumulado por época; o teste aparece somente antes da primeira atualização e depois da última, para não orientar o treino. PGM é um formato de imagem suficientemente simples para ser escrito em poucas linhas, sem incorporar uma biblioteca de imagens. A opção --data muda o arquivo, --output muda o diretório, --no-download exige uma cópia local íntegra e --self-test executa somente as verificações do leitor, do gradiente e do relatório físico.
Na execução usada para este artigo, em um Intel Core i7-10750H, a configuração didática levou 5,23 s depois da leitura e da verificação do conjunto. Os números são uma observação de reprodutibilidade, não um benchmark de C++ contra outra implementação:
| Momento | MSE no treino | MSE na validação | MSE no teste |
|---|---|---|---|
| Antes da primeira atualização | 0,085222 | 0,086178 | 0,085440 |
| Depois de 12 épocas | 0,020623 | 0,020876 | 0,020541 |
O MSE de teste caiu 75,96%, e a proximidade entre treino, validação e teste mostra que o ganho não ficou restrito aos 10.000 exemplos que produziram gradientes. Dezesseis valores latentes substituem 784 valores normalizados, uma redução dimensional de 49 vezes. Isso não significa automaticamente compressão de arquivo de 49 vezes: a entrada original ocupa um byte por pixel, enquanto cada latente ocupa quatro bytes em FP32, e o custo dos pesos e da média também precisaria entrar numa contabilidade física completa.
A Figura 2 mostra oito exemplos sorteados da partição oficial de teste. A fileira superior contém as imagens originais e a inferior, as reconstruções produzidas depois das 12 épocas. Silhueta, altura do cano e distribuição geral de massa atravessam o gargalo; contornos finos, textura e alguns detalhes que distinguem peças semelhantes tornam-se borrados. É exatamente o comportamento que a teoria prevê para uma projeção linear de baixa dimensão: ela conserva direções globais de grande variação, não aprende uma regra localizada para desenhar cada borda.
Figura 2: Um gargalo linear com 16 valores conserva as formas dominantes das roupas de teste, mas transforma detalhes locais em borrões mesmo após reduzir o MSE em 75,96%.
O experimento confirma que o autoencoder aprende algo utilizável: um código curto capaz de reconstruir exemplos nunca usados na atualização. Ele não confirma que esse código separa classes, detecta anomalias ou serve a toda tarefa posterior. Os rótulos não participaram do treino e não treinamos um classificador sobre $z$. A validação monitora a configuração fixada, mas não fizemos uma busca ampla de hiperparâmetros nem comparamos o tempo a uma BLAS. Essas ausências são limites do resultado, não notas de rodapé inconvenientes. A utilidade semântica da representação continua sendo uma pergunta separada da reconstrução, precisamente a advertência construída nas Seções 3 e 7.
6. O custo físico do gargalo
Agora que o programa treinou o autoencoder sobre imagens reais, podemos abandonar um exemplo hipotético e auditar exatamente o que foi alocado e calculado. A configuração didática usa $D=784$ pixels, $d=16$ valores latentes, minilotes de $B=128$ imagens e $N=10\,000$ exemplos de treino. Como todos esses números já pertencem ao executável da Seção 5, o próprio programa produz o relatório que fundamenta esta seção.
Uma distinção de unidades evitará confusão. Um FLOP é uma operação de ponto flutuante; FLOPs será a contagem dessas operações; FLOP/s seria uma taxa dividida pelo tempo. Contaremos uma multiplicação e uma adição como dois FLOPs, mesmo quando o compilador puder reuni-las numa instrução FMA. Trata-se de uma contagem algorítmica, não de uma medição de desempenho.
6.1 O relatório produzido pelo programa
Antes de iniciar o cronômetro do treino, a configuração padrão imprime:
Custo físico estimado para esta configuração:
conjunto em RAM: 54880000 bytes de pixels + 70000 bytes de rótulos
pesos ligados: 12544 FP32 = 50176 bytes (49.00 KiB)
gradiente + momento: 100352 bytes (98.00 KiB)
buffers por imagem: 6400 bytes (6.25 KiB)
pesos + média + buffers de treino: 160064 bytes (156.31 KiB)
entrada FP32 -> latente FP32: 3136 -> 64 bytes (49.00x)
imagem uint8 -> latente FP32: 784 -> 64 bytes (12.25x)
passagem direta: 50176 FLOPs/imagem; 6422528 FLOPs/lote cheio
núcleo de treino: 125440 FLOPs/imagem; 1259354880 FLOPs/época
Esses valores são calculados a partir de dataset.size(), model.dimensions, model.latent_dimensions, config.train_count e config.batch_size, não copiados para uma mensagem fixa. Um autoteste ainda confere os resultados esperados para a configuração didática. Assim, se mudarmos $d$, $B$ ou $N$ no programa, o relatório muda junto com o experimento e uma divergência acidental deixa de passar silenciosamente.
A contagem de memória considera o conteúdo lógico dos vetores, sem metadados internos de std::vector, capacidade excedente, índices das partições, a linha temporária usada pelo leitor ou os buffers de entrada e saída de arquivos. A contagem de treino também exclui centralização, embaralhamento, cálculo das métricas e E/S. Essas fronteiras são parte da definição do número.
6.2 Redução dimensional não é automaticamente compressão de arquivo
A imagem centralizada que entra na álgebra contém 784 valores FP32:
\[784\cdot4=3\,136\ \text{bytes}.\]O latente contém 16 valores do mesmo tipo:
\[16\cdot4=64\ \text{bytes}.\]Comparando representações FP32 dos dois lados, a redução física acompanha a redução dimensional:
\[\frac{3\,136}{64}=\frac{784}{16}=49.\]O arquivo original, porém, já armazena cada pixel como std::uint8_t. Uma imagem bruta ocupa 784 bytes, de modo que a comparação com o latente FP32 será
Portanto, “gargalo 49 vezes menor” descreve corretamente a quantidade de coordenadas e o tamanho relativo quando entrada e latente usam FP32. Não descreve sozinho um arquivo 49 vezes menor. Uma representação persistente também precisa definir o tipo numérico do latente, o formato de arquivo e, dependendo do uso, como distribuir entre muitas imagens os 50.176 bytes dos pesos e os 3.136 bytes da média. Um compressor completo precisaria ainda de serialização, quantização ou codificação entrópica e de uma comparação com formatos de imagem existentes. Nosso programa demonstra redução de representação, não reivindica um novo formato de compressão.
6.3 O estado que permanece em memória durante o treino
A matriz ligada possui
\[dD=16\cdot784=12\,544\]pesos FP32, equivalentes a 50.176 bytes ou 49 KiB. O acumulador de gradientes e a velocidade do momento repetem esse formato e ocupam juntos 100.352 bytes. A média de treino acrescenta $784\cdot4=3\,136$ bytes.
Os buffers reutilizados para uma imagem são input, error, latent e projected_error. Os dois primeiros possuem $D$ valores; os dois últimos, $d$. Logo,
Somando pesos, média, gradiente, momento e esses quatro buffers, obtemos 160.064 bytes, ou 156,31 KiB, de estado quente do treino. O conjunto de dados continua sendo a maior alocação: seus pixels ocupam 54.880.000 bytes e seus rótulos, 70.000 bytes.
O detalhe mais importante é o que não foi alocado. Embora $B=128$, o programa não materializa uma matriz FP32 $128\times784$. Ele lê uma imagem uint8_t, converte seus 784 pixels para o mesmo buffer input, acumula a contribuição no gradiente e reutiliza os buffers na imagem seguinte. O tamanho do minilote decide quantas contribuições serão reunidas antes de atualizar $W$; não multiplica por 128 a memória das ativações desta implementação.
6.4 Da passagem direta à época de treino
Para uma imagem, o encoder calcula $z=Wy$. Cada uma das $d$ linhas executa um produto interno de comprimento $D$, com aproximadamente $2Dd$ FLOPs. O decoder calcula $W^\top z$ e acrescenta outros $2Dd$. Assim, a passagem direta custa
\[4Dd=4\cdot784\cdot16=50\,176\ \text{FLOPs por imagem}.\]Para um minilote cheio de 128 imagens, a mesma conta soma
\[4BDd =4\cdot128\cdot784\cdot16 =6\,422\,528\ \text{FLOPs}.\]O treino precisa ainda projetar o erro, $q=We$, por $2Dd$ FLOPs, e acumular os dois produtos externos $ze^\top+qy^\top$, por aproximadamente $4Dd$ FLOPs. O núcleo por imagem será, portanto,
\[4Dd+2Dd+4Dd =10Dd =125\,440\ \text{FLOPs}.\]Ao fim de cada minilote, a atualização do momento e dos pesos percorre $Dd$ posições. São aproximadamente cinco operações por peso: duas multiplicações e uma adição para a velocidade, seguidas de uma multiplicação e uma subtração para $W$. Como $\lceil10\,000/128\rceil=79$, o núcleo de uma época contém
\[\begin{aligned} W_{\text{época}} &=N(10Dd)+\left\lceil\frac{N}{B}\right\rceil(5Dd)\\ &=10\,000\cdot125\,440+79\cdot62\,720\\ &=1\,259\,354\,880\ \text{FLOPs}. \end{aligned}\]Esse total descreve somente o núcleo que atualiza os pesos. O tempo de 5,23 s da Seção 5 inclui também uma avaliação completa do treino e da validação a cada época, além das avaliações de teste inicial e final. Por isso, dividir 1.259.354.880 pelo tempo observado não produziria uma taxa válida em FLOP/s. Precisaríamos contar todo o trabalho executado e adotar o protocolo de repetições de um benchmark.
6.5 Por que este código não chama BLAS
Algebricamente, um lote poderia ser organizado como $Z=XW_e$ e calculado por uma GEMM. Nosso programa escolhe outra execução: percorre uma imagem por vez e realiza produtos matriz-vetor equivalentes, com laços explícitos e um gradiente acumulado. Isso mantém visíveis $Wy$, $W^\top z$, $We$ e os dois produtos externos e evita uma dependência de álgebra linear, mas não pretende ser o caminho de maior desempenho.
Em uma versão de produção para CPU, uma BLAS reuniria imagens em matrizes, exploraria cache, SIMD e múltiplos núcleos. Numa GPU, lotes maiores ajudariam a ocupar unidades matriciais, mas aquisição, transferência e lançamentos de kernel também precisariam entrar na medição. A engenharia dessas operações pertence a GEMM o Coração Matemático. Aqui, a consequência é mais específica: reduzir $d$ diminui proporcionalmente pesos e aritmética dominante, mas o custo total continua dependente da representação dos dados, do algoritmo de execução e da máquina.
6.6 Laboratório: dimensão, memória e trabalho
Vamos começar pela predefinição Fashion-MNIST didático, que reproduz $D=784$, $d=16$, $B=128$ e $N=10\,000$. Depois vamos aumentar $d$ para 32: pesos e trabalho dominante dobram, mas os 784 bytes da imagem original permanecem iguais. Em seguida, vamos alterar apenas $B$. O custo de um minilote cheio e o número de atualizações mudam, enquanto os buffers por imagem permanecem constantes porque o programa não materializa uma matriz FP32 com o lote inteiro. Se escolhermos $d\ge D$, o laboratório continuará calculando os custos, mas avisará que a arquitetura deixou de possuir o gargalo subcompleto definido na Seção 1.
6.7 Exercícios de custo e memória
1. Compare o tamanho da imagem Fashion-MNIST com o latente de 16 valores quando ambos usam FP32 e quando a imagem usa uint8_t.
Solução: Em FP32, a imagem ocupa $784\cdot4=3\,136$ bytes e o latente ocupa $16\cdot4=64$ bytes. Logo,
\[\frac{3\,136}{64}=49.\]Com a imagem original em uint8_t, temos $784$ bytes e
A mesma redução dimensional produz razões físicas diferentes porque os tipos numéricos também são diferentes.
2. Calcule o número de pesos e os bytes usados com pesos ligados. Depois repita para duas matrizes independentes, sem vieses.
Solução: Com pesos ligados, existe uma matriz $16\times784$:
\[16\cdot784=12\,544\ \text{pesos}, \qquad 12\,544\cdot4=50\,176\ \text{bytes}=49\ \text{KiB}.\]Sem compartilhamento, encoder e decoder exigem duas matrizes:
\[2\cdot12\,544=25\,088\ \text{pesos}, \qquad 25\,088\cdot4=100\,352\ \text{bytes}=98\ \text{KiB}.\]3. Reproduza os 6.400 bytes de buffers por imagem e explique por que o resultado não é multiplicado por $B=128$.
Solução: input e error possuem $D=784$ valores cada; latent e projected_error possuem $d=16$ valores cada. Todos usam FP32:
O programa processa uma imagem, acumula sua contribuição no gradiente e reutiliza os quatro buffers. O minilote controla o momento da atualização, mas não mantém 128 conjuntos dessas ativações.
4. Calcule os FLOPs da passagem direta de um minilote cheio e do núcleo de treino de uma imagem.
Solução: Para a passagem direta,
\[4BDd =4\cdot128\cdot784\cdot16 =6\,422\,528\ \text{FLOPs}.\]O núcleo de treino acrescenta a projeção do erro e os dois produtos externos, totalizando
\[10Dd =10\cdot784\cdot16 =125\,440\ \text{FLOPs por imagem}.\]5. Mantendo $D=784$ e $B=128$, determine o que acontece com os pesos e os FLOPs da passagem direta quando $d$ passa de 16 para 32.
Solução: Pesos e trabalho são lineares em $d$, portanto ambos dobram. A matriz passa a ter
\[32\cdot784=25\,088\ \text{pesos},\]ou $25\,088\cdot4=100\,352$ bytes, equivalentes a 98 KiB. A passagem direta do lote passa de 6.422.528 para
\[4\cdot128\cdot784\cdot32 =12\,845\,056\ \text{FLOPs}.\]7. O que atravessa o gargalo
O experimento do Fashion-MNIST oferece uma resposta concreta à pergunta que abriu o artigo. Um autoencoder linear conseguiu substituir 784 pixels normalizados por 16 valores e reconstruir imagens de teste com MSE de $0{,}020541$. Nas reconstruções, atravessaram o gargalo as silhuetas, a altura dos calçados e a distribuição geral de massa. Ficaram para trás bordas finas, textura e parte das diferenças entre peças visualmente próximas. O latente conservou aquilo que mais ajudou seu decoder linear sob a perda quadrática.
Cada código $z$ possui 16 valores FP32, equivalentes a 64 bytes, mas existe apenas durante a passagem da imagem pelo programa. O executável atual não serializa $W$, não grava a média de treino e não persiste os códigos do conjunto. Ele demonstra uma representação reduzida e treinável; não entrega ainda um formato de arquivo nem um compressor pronto para reutilização. Essa fronteira impede que a redução dimensional de 49 vezes receba, por distração, uma promessa de compressão que o programa não implementou.
A utilidade semântica também permanece em aberto. Podemos medi-la com uma sonda linear, vizinhos mais próximos, transferência ou uma tarefa do domínio. Não treinamos nenhuma dessas avaliações. Reconstruir exemplos nunca usados na atualização demonstra generalização da reconstrução, não demonstra que as classes ficaram linearmente separáveis nem que o código serviria como estado para prever ações e futuros.
Resta uma ponte arquitetural antes da difusão latente. Nosso modelo achata a imagem e produz um vetor global por uma transformação linear. Um autoencoder usado por difusão precisa, em geral, de transformações não lineares que preservem organização espacial em um tensor latente e de alguma regularização que torne esse espaço adequado ao modelo generativo. O novo Artigo 3, Autoencoders Espaciais: da Projeção Linear ao Latente Gerativo, construirá essa passagem sem abandonar a pergunta da série: o que a representação conserva, por qual pressão de treino e para qual uso posterior. Depois dele, a difusão latente congelará um autoencoder apropriado e treinará outro modelo dentro do espaço definido por ele.
O gargalo não descobre sozinho o que importa. A perda decide o preço de esquecer, mesmo quando cobra pela coisa errada.
8. Acrônimos e Abreviações neste artigo
A seguir está a lista de todos os acrônimos e abreviações identificados no texto, organizados em ordem alfabética com o termo original em inglês e a tradução para o português:
| Acrônimo / Abreviação | Definição em Inglês | Tradução em Português |
|---|---|---|
API |
Application Programming Interface | Interface de Programação de Aplicações |
ARFF |
Attribute-Relation File Format | Formato de Arquivo Atributo-Relação |
BLAS |
Basic Linear Algebra Subprograms | Subprogramas Básicos de Álgebra Linear |
CPU / CPUs |
Central Processing Unit | Unidade Central de Processamento |
CSV |
Comma-Separated Values | Valores Separados por Vírgula |
FLOP / FLOPs / FLOP/s |
Floating Point Operation / Floating Point Operations / Floating Point Operations per Second | Operação de Ponto Flutuante / Operações de Ponto Flutuante / Operações de Ponto Flutuante por Segundo |
FMA |
Fused Multiply-Add | Multiplicação e Adição Fundidas |
FP32 |
32-bit Floating Point | Ponto Flutuante de 32 bits |
GEMM |
General Matrix Multiply | Multiplicação Geral de Matrizes |
GPU |
Graphics Processing Unit | Unidade de Processamento Gráfico |
HTTP |
Hypertext Transfer Protocol | Protocolo de Transferência de Hipertexto |
IA |
Artificial Intelligence | Inteligência Artificial |
I-JEPA |
Image Joint-Embedding Predictive Architecture | Arquitetura Preditiva de Incorporação Conjunta de Imagem |
IDX |
Index File Format | Formato de Arquivo Indexado |
JEPA |
Joint-Embedding Predictive Architecture | Arquitetura Preditiva de Incorporação Conjunta |
KiB |
Kibibyte | Kibibyte |
MAE |
Masked Autoencoder | Autocodificador Mascarado |
MD5 |
Message-Digest Algorithm 5 | Algoritmo de Resumo de Mensagem 5 |
MiB |
Mebibyte | Mebibyte |
MSE |
Mean Squared Error | Erro Quadrático Médio |
MSVC |
Microsoft Visual C++ | Microsoft Visual C++ |
PCA |
Principal Component Analysis | Análise de Componentes Principais |
PGM |
Portable Graymap Format | Formato Portátil de Mapa de Cinza |
RAM |
Random-Access Memory | Memória de Acesso Aleatório |
SIMD |
Single Instruction, Multiple Data | Instrução Única, Múltiplos Dados |
SimCLR |
Simple Framework for Contrastive Learning of Visual Representations | Estrutura Simples para Aprendizado Contrastivo de Representações Visuais |
TLS |
Transport Layer Security | Segurança da Camada de Transporte |
Referências
ALAIN, G.; BENGIO, Y. What Regularized Auto-Encoders Learn from the Data-Generating Distribution. Journal of Machine Learning Research, v. 15, p. 3743-3773, 2014. Disponível em: https://www.jmlr.org/papers/v15/alain14a.html. Acesso em: 29 jul. 2026.
BALDI, P.; HORNIK, K. Neural Networks and Principal Component Analysis: Learning from Examples Without Local Minima. Neural Networks, v. 2, n. 1, p. 53-58, 1989. Disponível em: https://doi.org/10.1016/0893-6080(89)90014-2. Acesso em: 28 jul. 2026.
HINTON, G. E.; MCCLELLAND, J. L. Learning Representations by Recirculation. Advances in Neural Information Processing Systems, v. 1, 1988. Disponível em: https://proceedings.neurips.cc/paper_files/paper/1987/hash/afacc5db3e0e85b446e6c7727cd7dca5-Abstract.html. Acesso em: 29 jul. 2026.
HINTON, G. E.; SALAKHUTDINOV, R. R. Reducing the Dimensionality of Data with Neural Networks. Science, v. 313, n. 5786, p. 504-507, 2006. Disponível em: https://doi.org/10.1126/science.1127647. Acesso em: 28 jul. 2026.
HINTON, G. E.; ZEMEL, R. S. Autoencoders, Minimum Description Length and Helmholtz Free Energy. Advances in Neural Information Processing Systems, v. 6, 1993. Disponível em: https://proceedings.neurips.cc/paper/1993/hash/9e3cfc48eccf81a0d57663e129aef3cb-Abstract.html. Acesso em: 29 jul. 2026.
MICROSOFT. BCryptOpenAlgorithmProvider function (bcrypt.h). Microsoft Learn. Disponível em: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider. Acesso em: 1 ago. 2026.
MICROSOFT. URLDownloadToFile function (Windows). Microsoft Learn. Disponível em: https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms775123(v=vs.85). Acesso em: 1 ago. 2026.
OPENML. Fashion-MNIST: dataset 40996, version 1. OpenML, 2017. Disponível em: https://www.openml.org/api/v1/json/data/40996. Acesso em: 1 ago. 2026.
RUMELHART, D. E.; HINTON, G. E.; WILLIAMS, R. J. Learning Internal Representations by Error Propagation. In: RUMELHART, D. E.; MCCLELLAND, J. L. Parallel Distributed Processing: Explorations in the Microstructure of Cognition. Cambridge: MIT Press, 1986. v. 1, cap. 8, p. 318-362. Disponível em: https://www.cs.toronto.edu/~hinton/backprop.html. Acesso em: 29 jul. 2026.
VINCENT, P.; LAROCHELLE, H.; BENGIO, Y.; MANZAGOL, P.-A. Extracting and Composing Robust Features with Denoising Autoencoders. ICML, 2008. Disponível em: https://doi.org/10.1145/1390156.1390294. Acesso em: 28 jul. 2026.
XIAO, H.; RASUL, K.; VOLLGRAF, R. Fashion-MNIST: a Novel Image Dataset for Benchmarking Machine Learning Algorithms. arXiv:1708.07747, 2017. Disponível em: https://arxiv.org/abs/1708.07747. Acesso em: 1 ago. 2026.
ZALANDO RESEARCH. Fashion-MNIST. GitHub, 2017. Disponível em: https://github.com/zalandoresearch/fashion-mnist. Acesso em: 1 ago. 2026.
Índice da Série: Representações e Modelos de Mundo
- 1. Representações e Modelos de Mundo: o Mapa da Série
- 2. Autoencoders: o Gargalo, a Reconstrução e o Latente (Você está aqui)
- 4. Difusão Latente: Comprimir Antes de Gerar
- 5. SimCLR: Aprender por Contraste e Pagar pelos Negativos
- 6. BYOL: Aprender sem Negativos sem Entregar Tudo ao Colapso
- 7. DINO: Autodestilação e Objetos que Emergem da Atenção
- 8. MAE: Mascarar 75% da Imagem e Reconstruir o que Falta
- 9. Prever, não gerar: o template JEPA e o problema do colapso
(Updated: )