Na Parte 1 a gente construiu a máquina: template de e-mail com slots, pool de variantes por slot, tabela de pesos append-only e um loop de feedback que recalcula a alocação a cada poucas horas. Esta parte é sobre a pergunta que está no centro dela: como esse loop deveria transformar contagem em percentual?
Parece problema já resolvido na teoria, porque multi-armed bandit tem resposta canônica, e eu cobri o setup clássico no post sobre Multi-Armed Bandits. Só que a teoria quase nunca fala do que acontece quando a alocação é um número de produto publicado, a recompensa chega horas depois e duas variantes podem ser genuinamente equivalentes. Então este post é um guia de campo: as estratégias que servem para esse problema, a matemática de cada uma, onde elas brilham e onde elas quebram sem avisar.
O Que a Regra Precisa Entregar
A cada poucas horas, para cada slot de cada ação dinâmica, o otimizador recebe a contagem de 30 dias de cada variante (enviado, aberto, clicado) e tem que devolver uma coisa só: uma alocação percentual que soma 100. Contra as realidades desse sistema, uma boa regra precisa:
- Convergir: variante genuinamente melhor tem que terminar com a maior parte do volume.
- Ser estável no ruído: o peso é armazenado, plotado e lido por gente. Mudança do tamanho do ruído na contagem tem que produzir mudança do tamanho do ruído na alocação.
- Calibrar a aposta pela evidência: uma divisão 60/40 apoiada em 200.000 envios merece aposta mais forte que a mesma divisão apoiada em 2.000.
- Conviver com recompensa atrasada e em batch: clique chega por horas depois do envio, então não existe feedback por decisão, só recálculo em batch sobre contagem acumulada.
- Ter pouco botão para girar: toda constante ajustada na mão é um número que alguém vai ter que justificar, revisar e, uma hora, errar.
Nenhuma regra entrega os cinco de graça. Vamos colocar os dois candidatos sérios contra essa lista.
Estratégia 1: Probability Matching (Thompson Sampling)
A resposta bayesiana. Cada variante ganha uma posterior Beta sobre a taxa de recompensa dela, montada direto da contagem,
$$\theta_i \sim \text{Beta}(1 + \text{sucessos}_i,\ 1 + \text{fracassos}_i)$$
e aí cada variante recebe de alocação a própria probabilidade de ser a melhor: sorteia de todas as posteriores ao mesmo tempo (100.000 vezes) e conta em que fração dos sorteios a taxa de cada variante saiu no topo.
def allocate(slot): # um slot = um bandit independente
if not passes_cold_start(slot): # mais sobre essas travas abaixo
return uniform(slot.variants)
a, b = posterior_params(slot) # Beta(1 + sucessos, 1 + fracassos)
draws = rng.beta(a, b, size=(100_000, len(slot.variants))) # semente fixa
p_best = share_of_draws_where_each_variant_wins(draws)
return p_best * 100 # pesos somam 100 por slot
Forças. A aposta é calibrada pela confiança, e nenhum botão faz essa calibração. O P(best) de um vencedor claro sobe para ~100% na mesma velocidade em que a evidência fica mais forte. Variante estatisticamente empatada fica perto do uniforme. E variante sub-amostrada continua ganhando alguns sorteios, porque a posterior dela é larga e às vezes amostra alto, o que é exploração saindo de graça da matemática. Com semente fixa, a alocação é função pura da contagem: mesmo dado entra, mesmo peso sai.
Fraqueza, e é grande. Perto de um empate o P(best) oscila muito. Para duas variantes, a probabilidade posterior de a variante 1 ganhar da 2 é mais ou menos
$$P(\theta_1 > \theta_2 \mid \text{dados}) \approx \Phi(z), \quad z = \frac{\hat{p}_1 - \hat{p}_2}{SE}$$
e quando as taxas verdadeiras são iguais, $z$ é um passeio aleatório em volta do zero, mexendo em quantidades do tamanho do ruído conforme a contagem acumula. O $\Phi$ espalha esse vaivém por quase todo o intervalo (0,1): um $z$ passeando entre $-0{,}8$ e $+0{,}8$, puro ruído, joga o P(best) de 21% para 79%. Mais dado não resolve: numerador e erro padrão encolhem juntos, então $z$ é livre de escala. No limite, para empate exato, o P(best) calculado sobre dado novo fica praticamente uniforme entre 0 e 1. Também não é ruído de Monte Carlo: com 100.000 sorteios o erro de simulação é fração de ponto percentual. A instabilidade está no dado. Dá para ver o comportamento em dado sintético, com duas variantes de taxa de clique verdadeira igual:
Se essa fraqueza importa depende de para onde o número vai. Perto do empate ela custa quase nada de recompensa, porque se duas variantes performam igual, qualquer divisão entre elas rende o mesmo. (Guarde esse pensamento, ele é a percepção central da Parte 3.) Só que como número publicado, armazenado, plotado e lido todo dia pelo dono da campanha, uma alocação que diz 80/20 hoje e 25/75 amanhã com CTR estável parece quebrada mesmo quando não está. E toda métrica calculada a partir dos pesos herda a mesma instabilidade.
Estratégia 2: Alocação Proporcional
A resposta de engenharia. Pontue cada variante pela média da posterior e aloque proporcional a uma potência do score:
$$w_i \propto \max(\text{score}_i, 0)^{S}$$
O expoente $S$ regula a agressividade: $S = 0$ é uniforme, $S = 1$ é proporcional puro (variante duas vezes melhor leva o dobro do peso) e $S \to \infty$ chega perto de vencedor-leva-tudo. O $S$ tem que ser escolhido na mão. Um valor ajustado em produção fica em torno de $S = 6$.
Forças. É função suave e determinística da contagem: mudança do tamanho do ruído no dado mexe a alocação do tamanho do ruído, por construção. Variante empatada fica perto do uniforme de forma estável, ou seja, a regra reporta o empate em vez de inventar um vencedor diferente a cada recálculo. Como número publicável, ela é impecável.
Fraquezas. O $S$ é constante mágica. Ele codifica o trade-off exploração/aposta inteiro na mão, e não existe jeito principiado de escolher. Pior: a regra é cega ao volume de evidência. Uma divisão de 60/40 na taxa observada produz a mesma alocação com dois mil ou com duzentos mil envios. Confiança, que é justamente o que a maquinaria bayesiana existe para quantificar, nunca entra na fórmula.
As duas estratégias lado a lado, no mesmo dado simulado, com uma variante que tem vantagem relativa real de 20%:
As duas oscilam enquanto clique é escasso, porque nenhuma regra de alocação corre mais rápido que o dado dela. A diferença é o que acontece quando a evidência firma: o Thompson segue convertendo certeza em alocação, passa de 90%, de 95%, vai para 100%, enquanto a proporcional estaciona na constante em que a razão observada cai (aqui ~75%) e fica lá para sempre, mandando um quarto do volume para uma variante que ela já sabe há muito tempo que é pior. E se essa constante é 55% ou 75%, quem decidiu foi o $S$, não o dado.
Os Clássicos, e Por Que Não Servem
Para registro, as duas políticas clássicas de bandit. ε-greedy (aposte no líder atual e explore uniforme ao acaso com probabilidade ε) paga imposto permanente de exploração em perdedor conhecido e traz mais um botão para ajustar na mão. UCB (escolha de forma determinística o braço com maior limite superior de confiança) foi feito para escolher um braço por decisão, então não produz divisão percentual naturalmente. O formato desse sistema, com recálculo em batch, peso estático publicado e recompensa atrasada, descarta as duas antes de discutir detalhe fino.
O Que Roda em Produção: Thompson, Sob Condições
O probability matching ganha a comparação, porque a calibração por confiança é a propriedade pela qual a feature existe. Mas só ganha sob condições que neutralizam a instabilidade:
- Travas de cold-start. O slot fica em peso uniforme até toda variante ter 500 envios e, para recompensa baseada em clique, até o slot somar 50 cliques. A segunda trava importa mais do que parece: confiança acompanha clique, não envio, e um slot passa fácil de milhares de envios carregando clique de um dígito, onde toda taxa é ruído.
- Janela acumulada. A contagem agrega numa janela móvel de 30 dias, então entre duas execuções separadas por três horas o dado quase não muda. E função determinística de contagem que muda devagar muda devagar.
- Semente fixa. Zero tremida de Monte Carlo entre execuções. Peso que muda significa que o dado mudou.
- Recompensa densa por slot, que é a próxima seção e a maior alavanca de todas.
Uma ressalva honesta sobrevive a tudo isso: para variante de verdade equivalente, o P(best) ainda passeia, só que na escala de tempo da janela de 30 dias em vez de a cada execução. Nenhuma regra de alocação resolve pergunta que o dado não responde. Perceber que P(best) dividido para sempre significa "esse teste terminou, as variantes são equivalentes" exige outra estatística. Isso é a Parte 3.
Escolhendo a Recompensa: Julgue Cada Slot pelo Estágio Que Ele Controla
O que "sucesso" deveria significar naquelas posteriores Beta? A resposta natural, clique para todo slot, está sutilmente errada, e o motivo é um funil:
$$\underbrace{\frac{\text{cliques}}{\text{envios}}}_{\text{CTR entregue}} = \underbrace{\frac{\text{aberturas}}{\text{envios}}}_{\text{OR}} \times \underbrace{\frac{\text{cliques}}{\text{aberturas}}}_{\text{CTOR}}$$
Assunto e nome do remetente fazem o trabalho deles todo antes de o e-mail abrir. O único caminho causal deles até o clique passa pela abertura. Os slots do corpo (título, texto, CTA) só existem depois da abertura, então não podem causar uma. Aí cada slot é pontuado pelo estágio que ele controla de verdade:
| Slot | Recompensa | Posterior |
|---|---|---|
| assunto, nome do remetente | Taxa de Abertura (OR) | $\text{Beta}(1 + \text{abertos},\ 1 + \text{enviados} - \text{abertos})$ |
| slots do corpo (título, texto, CTA, ...) | Click-to-Open Rate (CTOR) | $\text{Beta}(1 + \text{clicados},\ 1 + \text{abertos} - \text{clicados})$ |
Dois argumentos independentes chegam nesse mesmo desenho:
- Densidade de sinal. Abertura é uns 50× mais abundante que clique. Assunto pontuado por abertura monta uma posterior estreita numa fração do tempo que levaria com clique. É a diferença entre teste que conclui em dias e teste que conclui no trimestre que vem.
- Tirar o confundimento. Variante de corpo pontuada por CTR simples herda a taxa de abertura do assunto com que ela foi pareada por acaso. Como o corpo não causa abertura, essa variância herdada é confundimento puro: um CTA ótimo pode perder o teste dele por ter caído junto de um assunto fraco. O CTOR condiciona na abertura e tira isso. Na outra direção vale o mesmo: assunto pontuado por clique pode ser punido por culpa de corpo fraco.
E como CTR entregue é exatamente o produto OR × CTOR, maximizar cada fator separado maximiza o produto. Escolha o assunto e o remetente que abrem melhor, escolha o corpo que converte melhor, e a combinação é o melhor CTR entregue que os pools conseguem dar. De bônus, isso deixa a suposição de independência da Parte 1 mais confortável: slot otimizado dentro de estágio separado do funil tem menos espaço para interagir que slot todo disputando a mesma métrica final.
O Preço da Cautela: Termos de Penalidade e Guardrails
O objetivo do sistema é direto: maximizar abertura e clique, as taxas do funil que cada slot controla. Duas adições bem-intencionadas tentaram deixar o otimizador mais cuidadoso que isso, e as duas cobraram mais do que devolveram.
A penalidade por descadastro. O refinamento óbvio é uma recompensa que enxerga custo:
$$\text{score} = \text{taxa de clique} - \lambda \cdot \text{taxa de descadastro}, \quad \lambda = 5$$
(leia: "um descadastro custa o mesmo que cinco cliques ganhos"). O problema é que descadastro é raro, muito mais raro que clique. No começo do teste, com contagem de descadastro entre zero e um punhado por variante, a penalidade é ruído em cima de quase nenhum evento, chacoalhando o score e portanto o peso, sem ganho de informação nenhum. E quando a campanha amadurece, a estimativa de descadastro de cada variante já assentou perto do mesmo valor, então o termo desloca todos os scores pela mesma constante e nunca inverte ranking. Ruído no começo, no-op no fim: o termo saiu. A recompensa segue sendo abertura e clique. Descadastro é monitorado fora da recompensa, que é onde alerta deve morar.
Os guardrails. Piso e teto ("nenhuma variante abaixo de 1%, nenhuma acima de 95%") existiam por um motivo defensável: manter algum dado entrando em toda variante, para que um vencedor coroado cedo por engano ainda pudesse ser derrubado pela evidência. Só que olhe onde a conta cai: no vencedor de verdade, para sempre. Com o piso ligado, o vencedor decidido de um slot nunca passa de ~95%. Parece imposto modesto de 5%, até você lembrar que são cinco slots, cada um pagando o imposto sozinho. A fração de e-mail montado inteiro com vencedor é
$$0{,}95^5 \approx 77\%$$
ou seja, quase um quarto de todo envio carrega pelo menos um componente que o dado já condenou. Não durante o aprendizado: permanentemente, em todo teste que já convergiu. E o seguro comprado com esse volume era quase todo imaginário. Rodando o alocador de novo sobre contagem real acumulada, o piso estava segurando ~51% das variantes perdedoras acima da probabilidade verdadeira de serem as melhores, que num teste convergido é quase sempre ~0%.
Então: sem piso, sem teto. Vencedor claro leva 100% do slot, perdedor claro vai para 0% exato. O risco que sobra é real e vale escrever: variante em 0% não recebe tráfego, então se o ranking verdadeiro virar depois, por sazonalidade ou mudança de público, o sistema não tem como perceber. Esse trade foi feito de olho aberto: eficiente por padrão, com o risco documentado do lado das constantes.
Estimando o Lift Sem Prometer Demais
A última peça fica no lado da leitura, e responde a pergunta real do usuário: "o que esse teste está me dando?" O número de destaque do produto é o lift estimado de CTR da melhor combinação (a variante top de cada slot) sobre a combinação padrão (a primeira variante programada de cada slot, que é o e-mail que você teria mandado sem teste nenhum). Sob a mesma independência por slot que o otimizador assume, o ganho de cada slot multiplica:
$$\text{lift} = \prod_{\text{slots}} \frac{\text{taxa}_{\text{melhor}}}{\text{taxa}_{\text{padrão}}} - 1$$
A multiplicação é o que deixa esse número empolgante, porque cinco slots 5% melhores cada compõem uma combinação +28%. E é também o que deixa ele perigoso, porque qualquer superestimativa por slot compõe com o mesmo entusiasmo.
E estimativa ingênua por slot superestima muito. Variante com 3 cliques em 100 envios tem taxa bruta de 3%. Se o CTR base da ação é 0,5%, esse slot sozinho contribui "+500% de lift" em cima do que é basicamente sorte no cara ou coroa. E o argmax seleciona exatamente esses acasos: é a maldição do vencedor, pegue o máximo de estimativas ruidosas e você normalmente pega a mais sortuda, não a melhor. Publique esse número bruto e o usuário vê um "+3.000% de lift" derreter para +12% nas semanas seguintes. O teste não piorou, o número nunca foi real, mas não é essa a sensação. E quem paga a conta é a credibilidade do dashboard.
O contra-ataque é shrinkage bayesiano empírico. A taxa exibida de cada variante é puxada na direção do CTR geral da ação $p_0$, com um prior que vale $\kappa = 20$ cliques:
$$\tilde{p} = \frac{\text{cliques} + \kappa}{\text{envios} + \kappa / p_0}$$
Variante sem dado lê exatamente $p_0$, ou seja, lift zero. Variante com 3 cliques mexe pouco o ponteiro. Variante com 300 cliques quase não encolhe, porque o dado domina o prior. O amortecimento é contínuo no volume de clique, o que é melhor que corte de amostra mínima: não existe um degrau em que o número de repente aparece pronto e ainda ruidoso. E o mais importante: "melhor" é selecionado pela taxa encolhida também, então variante sortuda de pouco clique não ganha slot por acaso.
Assim o lift exibido promete menos no começo e converge para a verdade conforme o clique acumula, que é a direção certa de errar. Número que só cresce até a promessa constrói confiança. Número que encolhe a partir dela gasta uma confiança que o produto não recupera.
Os Aprendizados
- Calibração ganha de botão. Probability matching com trava de cold-start não precisa de ajuste nenhum para apostar exatamente o que a evidência sustenta. Expoente de agressividade codifica o mesmo trade-off como um número que alguém tem que adivinhar.
- Recompense cada decisão pelo estágio que ela controla de verdade. A decomposição OR × CTOR compra mais velocidade de convergência que qualquer ajuste.
- Seguro tem preço, então meça. O benefício de exploração do piso era quase todo imaginário. O custo dele era volume real em variante condenada.
- Encolha qualquer coisa que você exibe. Razão bruta mais argmax dá maldição do vencedor no dashboard.
O alocador, então, está resolvido: probability matching, recompensa por slot, trava de cold-start, sem guardrail. Só que alocação responde apenas "para onde vai o próximo envio?". O usuário pergunta outra coisa: "meu teste terminou, posso publicar o vencedor?" Acontece que "terminado" precisa de estatística própria, o arrependimento esperado, e isso é a Parte 3. Até lá.