Multi-Task Learning for Simultaneous Price, Volume, and Volatility Prediction
O aprendizado multitarefa (MTL) geralmente é vendido com uma afirmação: compartilhe um codificador entre alvos correlacionados e a tarefa principal ficará melhor. Na negociação, as metas correlacionadas são óbvias – os retornos, o volume e a volatilidade realizada caem todos do mesmo fluxo de ordens – e a afirmação quase nunca é testada. A questão interessante não é se as tarefas estão relacionadas. É se os gradientes compartilhados concordam e o que acontece nas dobras onde eles não concordam.
Este artigo coloca duas coisas no centro que a maioria dos artigos de MTL tratam como notas de rodapé:
- O balanceamento de perdas é o experimento, não um detalhe. Pesos fixos, ponderação de incerteza Kendall e GradNorm são três modelos diferentes. Execute todos os três nas mesmas dobras e relate os pesos aprendidos junto com a métrica da tarefa principal para cada um.
- A transferência negativa é mensurável antes de você ver a métrica. A similaridade de cosseno entre gradientes de tarefas no codificador compartilhado informa, durante o treinamento, se as tarefas auxiliares estão puxando a representação para algum lugar que a tarefa primária deseja ir. Assine os cossenos e verifique se o sinal previu o resultado nessa dobra.
Todo o resto do pipeline – o processo de volatilidade, o ciclo de treinamento, os controles de vazamento, o protocolo de validação – já foi abordado em outras partes deste blog e está vinculado, em vez de derivado novamente.
Configuração

Dados recursos de entrada (OHLCV, indicadores técnicos, fluxo de pedidos), três metas:
- Tarefa 1 (primária): retorno do próximo período
- Tarefa 2 (auxiliar): volume de log do próximo período
- Tarefa 3 (auxiliar): volatilidade realizada no próximo período
Um modelo multitarefa produz todos os três simultaneamente, , e o risco multitarefa é uma soma ponderada dos riscos por tarefa:
Todo o artigo é sobre o e sobre o que os gradientes por tarefa fazem entre si.
Por que o treinamento conjunto pode ajudar, em um parágrafo. Tarefas auxiliares restringem a representação compartilhada a explicar mais de um fenômeno de mercado, que é um controle de capacidade e um viés indutivo ao mesmo tempo; e como o volume e a volatilidade são observados diretamente, enquanto o "retorno esperado" não o é, os cabeçotes auxiliares fornecem um sinal de gradiente mais limpo do que o cabeçote primário. O caso de um modelo que emite muitos resultados é discutido detalhadamente - com o mecanismo de interpretabilidade anexado - em transformadores de fusão temporal para previsão multi-horizonte, que apresenta o mesmo argumento de codificador compartilhado-muitas-cabeças para quantis multi-horizonte.
Arquitetura, brevemente
Compartilhamento rígido de parâmetros: um codificador compartilhado alimenta chefes específicos de tarefas , então . Esta é a versão medida aqui, porque é a versão onde o gradiente entra em conflito está bem definido.
O compartilhamento flexível de parâmetros dá a cada tarefa seu próprio codificador com uma penalidade de acoplamento — mais parâmetros, mais flexibilidade e nenhum vetor de parâmetros compartilhado único para medir conflitos. Redes de ponto cruz ficam no meio, misturando recursos por tarefa por meio de uma matriz aprendida em cada nível. Vale a pena tentar ambos se o compartilhamento difícil mostrar conflito e ambos estiverem fora do escopo da medição abaixo.
A experiência que importa: três esquemas de equilíbrio de perdas

A perda ingênua é sensível à escala. Se a perda de retorno persistir e perda de volume ao redor , o volume possui o gradiente e a cabeça de retorno morre de fome. Três respostas:
Pesos fixos. Definir depois de padronizar cada alvo. A linha de base honesta – se vencer, os esquemas adaptativos serão uma cerimônia.
Ponderação de incerteza (Kendall et al., 2018). Aprenda uma escala de ruído homocedástica por tarefa:
Tarefas de alta incerteza são automaticamente reduzidas; o termo bloqueia o trivial solução. Observe isso é um dispositivo de ponderação de perda de tempo de treinamento, não um intervalo preditivo — para incertezas com as quais você pode realmente dimensionar uma posição, consulte previsão conforme.
GradNorm (Chen et al., 2018). Equilibre magnitudes de gradiente em vez de escalas de perda. Cada etapa: calcular e a média , calcule a taxa de treinamento relativa e atualizar . Todas as tarefas são treinadas em taxas comparáveis, independentemente da escala de perdas.
O código específico do MTL são os cabeçalhos, o encaminhamento de retorno de lista e a agregação de perdas. A pilha Linear/BatchNorm/ReLU/Dropout, o padrão Adam/cosseno/clip e o loop de época são o padrão padrão mostrado em DeepLOB e são omitidos aqui.
import torch
import torch.nn as nn
class MultiTaskTradingModel(nn.Module):
"""Hard parameter sharing: one encoder, K heads."""
def __init__(self, encoder: nn.Module, repr_dim: int, n_tasks: int = 3):
super().__init__()
self.shared_encoder = encoder # any MLP/CNN/GRU trunk
self.task_heads = nn.ModuleList(
nn.Linear(repr_dim, 1) for _ in range(n_tasks)
)
def forward(self, x):
h = self.shared_encoder(x)
return [head(h).squeeze(-1) for head in self.task_heads]
def shared_repr(self, x):
return self.shared_encoder(x)
class UncertaintyWeightedLoss(nn.Module):
"""Kendall et al. (2018) homoscedastic weighting."""
def __init__(self, n_tasks: int = 3):
super().__init__()
self.log_vars = nn.Parameter(torch.zeros(n_tasks)) # log(sigma^2)
def forward(self, losses: list) -> torch.Tensor:
return sum(
torch.exp(-self.log_vars[i]) * loss + self.log_vars[i]
for i, loss in enumerate(losses)
)
def get_weights(self) -> list:
with torch.no_grad():
return [torch.exp(-lv).item() for lv in self.log_vars]
UncertaintyWeightedLoss tem parâmetros, então deve ir para o otimizador junto com o modelo: optim.Adam(list(model.parameters()) + list(uw.parameters()), ...). Esquecer isso é a maneira mais comum de "executar a ponderação da incerteza" e, em vez disso, executar silenciosamente pesos fixos.
O que denunciar
Para cada esquema, em cada dobra: os pesos da tarefa final aprendida, a métrica da tarefa primária e – como um esquema de ponderação é uma escolha de modelo – quantos esquemas foram comparados antes de escolher um.
| Esquema | Métrica de tarefa primária versus tarefa única | |||
|---|---|---|---|---|
| Fixo () | 1,00 | 1,00 | 1,00 | — |
| Ponderação da incerteza | — | — | — | — |
| GraduaçãoNorm | — | — | — | — |
Três esquemas vezes várias dobras já é uma pequena busca de modelo. Qualquer melhoria relatada aqui deve sobreviver à correção de testes múltiplos descrita em Sharpe deflacionado e testes múltiplos antes que signifique alguma coisa.
Transferência Negativa: Assine os Gradientes

Esta é a parte que vale a pena manter. A transferência negativa ocorre quando as tarefas auxiliares pioram a tarefa primária e tem um diagnóstico direto: o ângulo entre os gradientes da tarefa no espaço de parâmetros compartilhado.
Medido apenas no codificador compartilhado - os cabeçotes são específicos da tarefa por construção e sempre "concordam" trivialmente.
import torch.nn.functional as F
def shared_grad(model, x, y, task_idx, criterion=nn.MSELoss()):
"""Gradient of task `task_idx` w.r.t. the shared encoder, flattened."""
model.zero_grad(set_to_none=True)
loss = criterion(model(x)[task_idx], y)
loss.backward()
return torch.cat([
p.grad.detach().flatten()
for p in model.shared_encoder.parameters()
if p.grad is not None
])
def task_conflict(model, x, y_by_task, task_names):
"""Pairwise cosine similarity between per-task shared-encoder gradients."""
grads = {
name: shared_grad(model, x, y_by_task[name], i)
for i, name in enumerate(task_names)
}
return {
(a, b): F.cosine_similarity(
grads[a].unsqueeze(0), grads[b].unsqueeze(0)
).item()
for i, a in enumerate(task_names)
for b in task_names[i + 1:]
}
Chame isso em um lote prolongado em uma cadência fixa durante o treinamento, e não uma vez no final. Um par pode começar alinhado e divergir à medida que o codificador se especializa; um único número de final de treinamento esconde isso.
A descoberta a ser procurada – e publicada de qualquer maneira:
| Par | cos sim, treinamento precoce | cos sim, treinamento tardio | MTL ajudou na tarefa principal? |
|---|---|---|---|
| retorno ↔ volume | — | — | — |
| retorno ↔ volatilidade | — | — | — |
| volume ↔ volatilidade | — | — | — |
Se os gradientes de volume e volatilidade concordarem entre si, embora ambos entrem em conflito com o gradiente de retorno, a conclusão correta é que as duas tarefas auxiliares formam um bloco coerente ao qual a tarefa de retorno não pertence — e a solução é o agrupamento de tarefas, e não mais capacidade. Quando o conflito é real, as soluções padrão são PCGrad (Yu et al., 2020), que projeta cada gradiente conflitante no plano normal do outro; CAGrad (Liu et al., 2021), que busca uma direção de descida que não prejudique nenhuma tarefa; ou abandonando totalmente a tarefa auxiliar.
Observe o que está deliberadamente ausente: um gráfico t-SNE da representação compartilhada colorido pelo valor alvo. É decorativo - os números de cosseno acima dizem tudo o que a incorporação indicaria, e dizem isso como números.
Protocolo de validação

A medição acima é inútil sob um protocolo desleixado, e o MTL piora as armadilhas usuais porque há três alvos para vazar em vez de um.
Dados reais, não um simulador. As metas devem vir de dados reais de OHLCV/comércio. Um brinquedo GARCH codificado gera volatilidade que está correlacionada com retornos por construção, que é precisamente o que está sendo testado – o experimento estaria medindo seu próprio gerador. Se você deseja um processo de volatilidade ajustado, previsão de volatilidade GARCH para criptografia ajusta GARCH(1,1) pela probabilidade máxima em BTC/ETH real e valida os resíduos padronizados, e GARCH assimétrico e o efeito de alavancagem cobre por que um simulador de resposta simétrica gaussiana distorce a volatilidade da criptografia em primeiro lugar. Os dados sintéticos são defensáveis apenas quando fornecem informações básicas controladas – uma correlação de tarefas conhecida e definida pelo autor que você está tentando recuperar – o que é um experimento diferente deste aqui.
Os escalonadores cabem apenas no trem. Coloque o escalonador de recursos e todos os três escalonadores de destino dentro de cada dobra de treinamento e aplique-os à validação; um mundo fit_transform antes de dividir os momentos do conjunto de testes de vazamentos em treinamento. Essa falha exata está catalogada na [taxonomia de viés de antecipação](/en/blog/post/taxonomia de viés de antecipação).
** Dobras walk-forward eliminadas e embargadas.** Uma divisão cronológica 80/20 não consegue distinguir uma melhoria de MTL de um efeito de dobra - esse é todo o argumento da otimização walk-forward, que mostra três divisões produzindo três conclusões. Reutilize a janela de expansão purged_walk_forward gerador de modelagem de propagação com aprendizado de máquina: ele elimina uma lacuna de horizon linhas em ambos os lados de cada limite, o que é importante aqui porque as janelas sobrepostas de volatilidade realizada vazam através do limite mesmo quando o alvo de retorno não o faz.
Uma linha de base clássica. Uma rede MTL que supera três redes de tarefa única não provou nada se um modelo de aumento de gradiente ou cume por alvo superar todas as quatro. Encaixe um modelo por alvo com LightGBM ou cumeeira nas mesmas dobras e nas mesmas características, e relate na mesma tabela.
| Modelo | Métrica de tarefa primária | Notas |
|---|---|---|
| Ridge, por alvo | — | Linha de base clássica |
| LightGBM, por destino | — | Linha de base clássica |
| MLP de tarefa única, por destino | — | Três redes separadas |
| MTL, melhor esquema de perdas | — | Uma rede, três cabeças |
O que faria o MTL valer a pena aqui

Condições sob as quais o MTL deve vencer, declaradas como hipóteses para verificar as dobras acima, e não como uma lista de verificação:
- As etiquetas auxiliares são mais limpas que a etiqueta primária. O volume é observado diretamente; "retorno esperado" não é. Se a cabeça de retorno for principalmente ruído de ajuste, o sinal gradiente das cabeças auxiliares é a única parte bem posicionada da objetiva.
- Os dados de treinamento são limitados em relação à capacidade do codificador, portanto a restrição auxiliar faz um trabalho real de regularização, em vez de apenas competir por parâmetros.
- A latência de inferência é importante e um passe para frente é melhor que três.
E o argumento contra, igualmente testável: se o valor medido cos_sim(return, ·) os valores são persistentemente negativos, o codificador compartilhado está sendo afastado da tarefa primária e os cabeçotes auxiliares são um imposto, não um regularizador.
Conclusão

Retornos, volume e volatilidade vêm da mesma microestrutura, portanto, uma representação compartilhada é um anterior razoável – mas um anterior não é um resultado. As duas coisas que esta configuração pode realmente estabelecer são qual esquema de equilíbrio de perdas os dados preferem (com os pesos aprendidos relatados, não apenas o vencedor nomeado) e se os gradientes da tarefa no codificador compartilhado concordam, medidos durante o treinamento, em vez de assumidos a partir do fato de que os alvos estão correlacionados.
Se as dobras de avanço eliminadas mostrarem que a rede MTL não conseguiu superar um modelo de aumento de gradiente por alvo, essa é a descoberta e ela será publicada como tal - o modelo é o negativo honesto. Um resultado negativo sobre uma transferência negativa ainda é um resultado sobre uma transferência negativa.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.