A pergunta
Dois agentes vão de A até B. Cada um sabe fazer uma coisa só: olhar para B, escolher uma direção e andar em linha reta. Os dois erram a direção. O que muda entre eles é o tamanho do passo e o tamanho do erro. O eficiente é eficiente no sentido que a palavra tem quando se fala de produtividade com IA: cobre muito mais terreno por passo e, em troca, erra mais a direção. Os dois chegam. Quem chega antes, andando quanto e com que previsibilidade de prazo, não é o que a intuição sugere.
Efetivo
\(L = 0{,}47\) · \(\sigma = 22^\circ\)
Também erra feio a mira, mas anda pouco por passo, então o erro não tem espaço para virar desvio. Chega em 23 passos.
Eficiente
\(L = 1{,}80\) · \(\sigma = 35^\circ\)
Cobre quase 4x mais terreno por passo e erra 1,6x mais a direção. Chega perto de B em 6 passos e depois orbita.
Adaptativo
\(L_k = c\,d_k\) · \(\sigma = 35^\circ\)
A mesma mira torta do eficiente. Só encolhe o passo conforme se aproxima. Chega em 8 passos na mediana.
O modelo
O agente opera em malha fechada: re-mira em B a cada passo. Isso importa, porque mirar uma vez só e andar reto é trivial. Com re-mira, um passo é isto:
Com \(d_k\) = distância até B, dá para escrever o que acontece depois de um passo. O termo negativo aproxima, o positivo afasta:
Existe uma distância em que os dois termos se anulam e o progresso esperado zera. Igualando \(2 L d_k c = L^2\):
Esse \(d^{*}\) é um piso: não importa quantos passos você dê, em média não se desce abaixo dele, e o agente acaba orbitando um anel de raio \(d^{*}\) em volta do alvo. Repare no que a fórmula diz: o piso é proporcional ao passo. Dobrar o passo dobra a distância mínima em que dá para parar. Com o alvo de raio \(\rho = 0{,}35\):
| Perfil | \(L\) | \(\sigma\) | \(c = e^{-\sigma^2/2}\) | \(d^{*} = L/2c\) | Entra no alvo? |
|---|---|---|---|---|---|
| Efetivo | 0,47 | 22° | 0,9289 | 0,253 | sim, com folga |
| Eficiente | 1,80 | 35° | 0,8298 | 1,085 | não, para nunca |
1,085 é três vezes o raio do alvo. O eficiente não erra por azar, erra por construção: o passo dele é grande demais para o próprio erro de mira.
Os números medidos
A fórmula é bonita, mas descreve mesmo a simulação? 3.000 execuções de cada perfil, sementes diferentes, orçamento de 400 passos:
| Perfil | Chegou | Mediana | Média | p90 | Pior caso | Caminho andado |
|---|---|---|---|---|---|---|
| Efetivo | 100% | 23 | 22,9 | 24 | 26 | 10,8 |
| Eficiente | 100% | 28 | 37,9 | 84 | 294 | 50,4 |
| Adaptativo | 100% | 8 | 8,4 | 10 | 15 | 14,2 |
O agente eficiente chega. Só que não por convergência: por sorteio. Ele orbita o anel e, de vez em quando, um passo aleatório o joga dentro do alvo. Por isso a mediana (28) é bem menor que a média (37,9), e por isso existe uma cauda que vai até 294 passos. Enquanto isso ele andou 50,4 unidades para percorrer um trajeto de 10,0. O efetivo andou 10,8.
Olhe as caudas, que é onde mora a diferença prática. O efetivo vai de 23 passos na mediana a 26 no pior caso das 3.000 execuções. O eficiente vai de 28 a 294. Os dois erram a mira; só um converte o erro em imprevisibilidade de prazo.
A distância média do eficiente ao alvo, depois de estabilizar, foi 1,085. A fórmula previa 1,085. Foi aí que decidi que o vídeo valia a pena.
O desfecho
Se o problema é o passo ser grande demais perto do alvo, a correção é óbvia depois que você vê: faça o passo encolher junto com a distância. Pondo \(L_k = \alpha\,d_k\) na recursão:
O piso some: a distância contrai por um fator constante a cada passo. E o \(\alpha\) ótimo é exatamente \(c\), o fator de pontaria, ou seja o passo que você pode se dar ao luxo de usar é definido pela sua precisão.
O agente adaptativo do vídeo mantém a mira péssima de 35° do eficiente e só encolhe o passo perto de B. Chega em 8 passos, contra 23 do preciso e 38 do eficiente. É o argumento inteiro: eficiência não substitui pontaria, é consequência dela.
Iterar rápido demais com IA
Lá em cima a palavra eficiente entrou com um sentido específico, e é hora de cobrar a dívida. Quando se fala de produtividade com IA, o ganho anunciado é sempre o mesmo: mais terreno por passo. Você pede uma feature, não uma função. Um arquivo, não um trecho. O \(L\) subiu, e subiu muito.
O que quase nunca entra na conta é que o \(\sigma\) sobe junto, pela mesma causa. Mira, aqui, é a fração do passo que aponta para onde você realmente queria ir, e ela depende de duas coisas: quão claro estava o pedido e quanto do que voltou você de fato leu. Peça 40 linhas e você lê as 40. Peça 600 e você passa o olho no diff, aprova, segue. O passo cresceu; a conferência não acompanhou. Como \(d^{*} = L/2c\), o raio de órbita piora nos dois lugares ao mesmo tempo: o numerador cresce e o denominador encolhe.
| No modelo | Iterando com IA |
|---|---|
| \(L\), tamanho do passo | O tamanho do pedaço que você pede de uma vez: uma função, um arquivo, uma feature inteira. |
| \(\sigma\), erro de mira | Quão longe do que você queria o resultado sai. Especificação vaga e contexto faltando somam aqui. |
| \(c = e^{-\sigma^2/2}\), fator de pontaria | Na prática, a fração do que voltou que você leu e conferiu de verdade. |
| \(d^{*} = L/2c\), raio de órbita | O quase pronto: a distância do fim abaixo da qual o trabalho para de convergir e passa a circular. |
| \(\alpha = c\), passo ótimo | O tamanho de pedido que a sua clareza atual comporta. |
Agora o detalhe que faz o estudo valer mais que a intuição. Olhe a mediana das 3.000 execuções: o eficiente chega em 28 passos, o efetivo em 23. Cinco passos. No caso típico, iterar acelerado é quase indistinguível de iterar devagar, e é justamente por isso que a prática se sustenta: a semana normal parece boa. A conta está fora da mediana, na média (37,9), no p90 (84) e no pior caso (294). Uma cauda dessas não se sente numa tarefa, se sente no trimestre, quando a estimativa que valia para o caso típico deixa de valer para qualquer coisa. O efetivo foi de 23 a 26 no pior caso das 3.000 execuções; o eficiente foi de 28 a 294.
E tem o caminho andado, que é a métrica mais honesta das três: 50,4 unidades para percorrer um trajeto de 10,0. Isso não é tempo parado esperando, é trabalho feito e desfeito. O código escrito e revertido, o refactor que voltou atrás, a terceira tentativa no mesmo bug. Ele sai em volume de diff, o que o torna especialmente traiçoeiro, já que volume de diff é o que costuma ser contado como produtividade. O efetivo andou 10,8.
O conserto não é usar menos IA, é o terceiro agente. O adaptativo mantém a mira de 35° do eficiente, péssima, intocada, e ainda assim chega em 8 passos contra 23 do efetivo. Ele não melhorou a pontaria: só parou de dar passo de 1,80 quando o que faltava até o alvo já era menor que o próprio passo.
É o que \(\alpha = c\) diz, traduzido. Passo grande vale enquanto você está longe, onde errar a direção custa pouco e explorar rende: esboço, andaime, protótipo descartável, código que ninguém vai manter. Perto do alvo o passo tem que encolher: integração, o bug que sobrou, o que vai para produção. A ferramenta não troca de marcha sozinha, porque gerar 600 linhas custa o mesmo prompt perto ou longe do fim. A decisão de encolher é sua, e ela é contraintuitiva exatamente onde mais importa: quanto maior o seu \(\sigma\), menor deveria ser o seu passo, e é quando você menos sabe o que quer que mais dá vontade de pedir tudo de uma vez.
Como fiz com o Manim
Manim é uma biblioteca Python: você declara objetos geométricos (mobjects) e anima transformações neles dentro de um método construct(), e o render sai em mp4 via ffmpeg. Use a Manim Community (v0.20.1 aqui), que é o fork mantido pela comunidade. O repositório pessoal do Grant, o manimgl, é otimizado para o fluxo dele, não para terceiros.
1. Instalação
Precisa de cairo e pango, de ffmpeg, e de LaTeX se você for usar MathTex: as equações são compiladas em LaTeX de verdade e convertidas em SVG por dvisvgm.
bash
# dependências de sistema (Arch; nos demais, os pacotes equivalentes)
sudo pacman -S --needed cairo pango ffmpeg \
texlive-basic texlive-latexextra texlive-fontsrecommended texlive-binextra
# a lib fica num venv local, fora do versionamento
python -m venv manim/.venv
manim/.venv/bin/python -m pip install manim
Em Python 3.14 não há wheels para pycairo e manimpango: o pip compila os dois do source, o que só funciona com os headers de cairo e pango instalados. Faltando, o erro fala de cairo.h, não de Python.
2. Separar o modelo da cena
A decisão que mais economizou tempo. Toda a matemática vive em sim.py, que só depende de numpy, e a cena importa dele. Assim 3.000 simulações rodam em segundos e o render nunca entra no laço de exploração.
manim/ ├── sim.py o modelo: perfis, um passo, a recursão. Só numpy. ├── efetividade.py a cena Manim. Só encenação. ├── seed_search.py escolhe a semente da execução que vai ao ar. ├── diagnostico.py os números de 3.000 execuções. ├── sweep.py varredura de parâmetros. ├── requirements.txt ├── .venv/ gitignore ├── media/ gitignore: cache de render, centenas de MB └── out/ gitignore: frames de inspeção
sim.py
@dataclass
class Perfil:
nome: str
passo: float
sigma_graus: float
adaptativo: bool = False
@property
def c(self) -> float:
"""E[cos eps] para erro gaussiano: o 'fator de pontaria', em (0, 1]."""
return math.exp(-(self.sigma ** 2) / 2)
@property
def raio_orbita(self) -> float:
"""d* = L / (2c). Distância abaixo da qual o agente não desce."""
return self.passo / (2 * self.c)
3. Escolher a semente sem trapacear
O modelo é aleatório, mas o vídeo mostra uma execução só. Existe uma tentação óbvia aqui: rodar até achar a semente em que o agente eficiente se dá mal de forma espetacular. Isso seria fabricar o resultado.
O que fiz foi diferente. Defini critérios de legibilidade (o eficiente precisa liderar de forma visível no começo, o efetivo precisa chegar perto da mediana medida) e depois contei quantas sementes passavam. Foram 1.081 de 2.999, ou seja 36,0%. A execução que está no vídeo é o caso comum, não a exceção. Esse número está impresso na saída do script justamente para poder ser cobrado.
Os dois agentes usam a mesma semente. Isso é de propósito: mesma sequência de azar, estratégias diferentes. É um experimento controlado, não duas corridas separadas.
4. O truque que faz a animação fluir
A forma ingênua de animar 26 passos é chamar self.play() 26 vezes. O resultado é picotado, porque cada chamada tem começo e fim próprios, e o render fica lento. A forma certa no Manim é um ValueTracker: um número animável que serve de relógio, mais updaters que redesenham os objetos em função dele. Aí toda a corrida cabe em uma chamada só, com velocidade constante.
Cada passo é desenhado como uma seta, não como um trecho de linha. É o que o modelo diz que ele é: um vetor, com magnitude e ângulo. E tem um efeito colateral ótimo no último ato, onde as setas visivelmente encolhem conforme o agente adaptativo se aproxima do alvo.
efetividade.py
class Agente:
"""Um vetor por passo, revelados em sequência por um ValueTracker."""
def __init__(self, pontos, cor, espessura, raio, ponta):
self.pontos = pontos
self.t = ValueTracker(0.0)
self.opacidade = ValueTracker(1.0) # escurecer sem brigar com o updater
# Setas dos passos concluídos. A geometria NUNCA é mexida: só a opacidade.
self.setas = VGroup(*[
Arrow(em_3d(a), em_3d(b), **self._estilo)
for a, b in pairwise(pontos)
])
self.setas.set_opacity(0)
self.setas.add_updater(self._atualizar_setas)
# A seta do passo em curso é um objeto à parte, refeito a cada quadro.
self.corrente = VMobject()
self.corrente.add_updater(self._atualizar_corrente)
def _atualizar_setas(self, grupo):
concluidos = int(np.floor(self.t.get_value()))
alfa = self.opacidade.get_value()
for i, seta in enumerate(grupo):
seta.set_opacity(alfa if i < concluidos else 0.0)
def _atualizar_corrente(self, m):
t = self.t.get_value(); k = int(np.floor(t)); fracao = t - k
# Abaixo de 5% do passo a seta é degenerada e o Manim não a orienta.
if k >= len(self.pontos) - 1 or fracao <= 0.05:
m.set_opacity(0); return
fim = self.pontos[k] + fracao * (self.pontos[k + 1] - self.pontos[k])
m.become(Arrow(em_3d(self.pontos[k]), em_3d(fim), **self._estilo))
m.set_opacity(self.opacidade.get_value())
# a corrida inteira, os dois agentes em lockstep, numa chamada:
self.play(
efetivo.t.animate.set_value(24),
eficiente.t.animate.set_value(24),
run_time=6.2,
rate_func=linear, # sem ease: passo tem que ter cadência constante
)
Três detalhes que só aparecem quando você erra. rate_func=linear é obrigatório: o padrão é smooth, que acelera no meio e destrói a leitura de cadência. Os updaters precisam de clear_updaters() antes do FadeOut, senão o objeto é redesenhado enquanto tenta desaparecer. E a opacidade também precisa de ValueTracker próprio, senão o updater sobrescreve qualquer .animate.set_opacity() no quadro seguinte.
5. Paleta
Preto e branco puro, como o Manim é por padrão. Sem cor, os agentes se distinguem por espessura: setas brancas finas (2,4 px) contra cinzas grossas (3,8 px), o que também carrega o significado, já que passo grande é literalmente traço mais pesado. A ordem de adição define o empilhamento, então o branco entra depois do cinza para não sumir no emaranhado. E a razão da ponta (max_tip_length_to_length_ratio) tem que ser menor no eficiente, 0,16 contra 0,30: as setas dele são 4x mais longas e com a mesma razão a cabeça vira um borrão perto de B.
6. Render e iteração
O ciclo que funcionou: renderizar rápido e feio, extrair quadros com ffmpeg, olhar, corrigir. Nunca assisti ao vídeo durante a iteração, só olhei frames em pontos escolhidos da linha do tempo.
bash
# iteração: 480p a 15 fps, uns 20 segundos de render
.venv/bin/manim -ql --disable_caching efetividade.py Efetividade
# inspeção: quadros em pontos-chave, mais rápido que assistir
for t in 4.5 21 30 38; do
ffmpeg -ss $t -i media/videos/efetividade/480p15/Efetividade.mp4 \
-frames:v 1 -y out/frames/f$t.png
done
# final: 1080p a 60 fps, cerca de um minuto em 16 núcleos
.venv/bin/manim -qh efetividade.py Efetividade
# compressão para a web: 4,5 MB viram 2,8 MB, com faststart
ffmpeg -i media/videos/efetividade/1080p60/Efetividade.mp4 \
-c:v libx264 -preset slow -crf 21 -pix_fmt yuv420p \
-movflags +faststart -an assets/video/efetividade-eficiencia.mp4
Dois flags que valem a explicação. --disable_caching: o Manim reaproveita trechos já renderizados, o que é ótimo em produção e péssimo quando você mexe em updaters, porque ele serve um trecho velho e você caça um bug já corrigido. -movflags +faststart: move o índice do mp4 para o começo do arquivo, senão o navegador baixa quase tudo antes de tocar.
O que deu errado
A parte que os tutoriais não mostram. Em ordem cronológica:
- O modelo estava fácil demais para o eficiente Com o alvo em raio 0,45 ele caía dentro por sorteio em 90% das execuções, e meu seed search estava filtrando justamente os 10% em que ele falhava. Isso é escolher o resultado a dedo. Varri o espaço de parâmetros procurando um regime que salvasse a tese e não achei nenhum: no melhor ponto ele ainda chegava em 37% das vezes. A correção não foi mexer nos parâmetros, foi trocar a pergunta. Medir quantos passos leva e quanto caminho anda, em vez de chega ou não, deu números mais fortes e que valem no caso típico.
- Um agente sem erro nenhum, e um diagnóstico errado no meio O efetivo chegava em exatamente 20 passos nas 3.000 execuções: mediana, p90 e pior caso iguais. Parece fraude. Culpei 10,0 ÷ 0,5 dar inteiro redondo e troquei o passo para 0,47; a variância continuou zero. A causa real era outra: com erro de 5°, cada passo entrega 99,6% do avanço pretendido e o ruído nunca soma um passo inteiro. O agente era determinístico. Só que isso tinha um sintoma que nenhuma tabela mostrava: no vídeo a trajetória dele é uma reta, e um agente que não erra apaga a premissa de que os dois erram. O reflexo de trocar a semente não resolveria nada, porque num sistema determinístico nenhuma semente muda o resultado. A alavanca era o σ. Subi para 22°, escolhido renderizando os candidatos lado a lado, porque "a trajetória parece torta?" é pergunta visual e não se responde por tabela.
-
A fonte padrão saiu serifada
O
Text()usa a fonte padrão do sistema via Pango, que aqui resolvia para uma serifada e brigava com a Computer Modern doMathTex. Fixeifont="Noto Sans"na interface e deixei serifa só nas equações. -
A ponta da seta descolando do marcador
Reaproveitei o mesmo objeto
Arrowpara o passo em curso e o concluído, chamandoput_start_and_end_on()a cada quadro. O método reescala a cabeça em função do novo comprimento e o erro acumula, até a ponta não bater mais com o destino. Correção: setas concluídas nunca têm a geometria tocada, só a opacidade, e a seta em curso é umVMobjectseparado que fazbecome()de umaArrownova por quadro. -
Três coisas que só o frame mostra
A chegada do efetivo sumia no emaranhado cinza (resolvido invertendo a ordem de adição e pondo um halo no ponto final). A legenda do ato da fórmula colidia com o anel tracejado, e depois de mover a fórmula para a esquerda esqueci de mover a que a substitui no
Transform, que voltava para o centro e colidia de novo. O ponto A ficou fora doFadeOutfinal e seguia aceso atrás da frase de encerramento. Nenhuma das três aparece lendo código.
Reproduzir
A pasta manim/ do repositório deste site tem os cinco arquivos Python e um requirements.txt. O venv, o cache de render e os frames de inspeção estão no gitignore, então clonar e rodar custa uma instalação:
bash
cd manim python -m venv .venv .venv/bin/python -m pip install -r requirements.txt .venv/bin/python sim.py # os pisos teóricos de cada perfil .venv/bin/python diagnostico.py # os números de 3.000 execuções .venv/bin/python seed_search.py # a semente e a fração de casos típicos .venv/bin/manim -qh efetividade.py Efetividade
Ressalvas
O \(d^{*}\) é o ponto fixo da recursão do valor esperado de \(d^2\), não a média da distribuição estacionária. Para o eficiente os dois coincidiram até a terceira casa, mas isso não é garantido em geral. O erro de mira também é suposto gaussiano e independente entre passos; viés sistemático só piora o quadro do agente de passo grande.
A distância só é conferida no fim de cada passo. Um passo longo pode passar raspando em B no meio do caminho e não contar. Isso é deliberado: a metáfora aqui é a de decisões discretas, em que o que importa é onde você para, não por onde você passou.
Referências
- Manim Community. Manim: a community-maintained Python library for creating mathematical animations. manim.community Versão 0.20.1 aqui. É o que se instala via pip.
- Sanderson, G. 3Blue1Brown. 3blue1brown.com Autor do Manim e do estilo visual que este vídeo imita.
- Robbins, H., & Monro, S. (1951). A Stochastic Approximation Method. Annals of Mathematical Statistics, 22(3), 400-407. As condições clássicas sobre o tamanho do passo; o passo adaptativo do último ato é um caso particular.
- Bottou, L., Curtis, F. E., & Nocedal, J. (2018). Optimization Methods for Large-Scale Machine Learning. SIAM Review, 60(2), 223-311. Formaliza por que passo constante estaciona num raio em torno do ótimo em vez de convergir: o mesmo fenômeno do d* daqui.
- Drucker, P. F. (1967). The Effective Executive. Harper & Row. Origem da distinção entre fazer as coisas certas e fazer as coisas do jeito certo.