Multi-Task Learning for Simultaneous Price, Volume, and Volatility Prediction
Multi-task learning (MTL) wordt meestal verkocht met de bewering: deel een encoder over gecorreleerde doelen en de primaire taak wordt beter. Bij de handel zijn de gecorreleerde doelstellingen duidelijk – rendementen, volume en gerealiseerde volatiliteit vallen allemaal buiten dezelfde orderstroom – en deze bewering wordt bijna nooit getest. De interessante vraag is niet of de taken met elkaar verband houden. Het gaat erom of de gedeelde gradiënten het eens zijn, en wat er gebeurt op de vouwen waar dat niet het geval is.
In dit artikel staan twee dingen centraal die de meeste MTL-artikelen als voetnoten behandelen:
- Verliesbalancering is het experiment, geen detail. Vaste gewichten, Kendall-onzekerheidsweging en GradNorm zijn drie verschillende modellen. Voer ze alle drie op dezelfde vouwen uit en rapporteer de geleerde gewichten naast de metriek voor de primaire taak voor elk.
- Negatieve overdracht is meetbaar voordat u de metriek ziet. De cosinusovereenkomst tussen taakgradiënten op de gedeelde encoder vertelt u tijdens de training of de hulptaken de representatie ergens naartoe trekken waar de primaire taak naartoe wil. Teken de cosinus en controleer vervolgens of het teken de uitkomst op die vouw voorspelde.
Al het andere dat in de pijplijn zit – het volatiliteitsproces, de trainingslus, de lekkagecontroles, het validatieprotocol – wordt al elders op deze blog besproken en is eerder met elkaar verbonden dan opnieuw afgeleid.
Installatie

Gegeven invoerfuncties (OHLCV, technische indicatoren, orderstroom), drie doelstellingen:
- Taak 1 (primair): rendement volgende periode
- Taak 2 (hulp): logvolume voor de volgende periode
- Taak 3 (hulp): gerealiseerde volatiliteit in de volgende periode
Een multi-taskmodel produceert ze alle drie tegelijk, , en het multitaskrisico is een gewogen som van de risico's per taak:
Het hele artikel gaat over de en over wat de gradiënten per taak met elkaar doen.
Waarom gezamenlijke training zou kunnen helpen, in één paragraaf. Hulptaken beperken de gedeelde vertegenwoordiging om meer dan één marktfenomeen te verklaren, namelijk een capaciteitscontrole en een inductieve bias tegelijk; en omdat volume en volatiliteit direct worden waargenomen terwijl "verwacht rendement" dat niet is, leveren de hulpkoppen een schoner gradiëntsignaal dan de primaire kop. Het pleidooi voor één model dat veel output uitzendt, wordt uitgebreid beargumenteerd – met de interpreteerbaarheidsmachinerie eraan vastgemaakt – in temporal fusion transformers for multi-horizon forecasting, wat hetzelfde gedeelde-encoder-many-heads argument aanvoert voor multi-horizon kwantielen.
Architectuur, in het kort
Hard parameter delen: een gedeelde encoder feeds taakspecifieke hoofden , Dus . Dit is de hier gemeten versie, omdat het de versie is waar gradiëntconflicten optreden is goed gedefinieerd.
Zacht delen van parameters geeft elke taak een eigen encoder met een koppelingsstraf – meer parameters, meer flexibiliteit en geen enkele gedeelde parametervector om conflicten op te meten. Kruissteeknetwerken zitten ertussenin, waarbij functies per taak worden gecombineerd via een aangeleerde matrix op elk niveau. Beide zijn het proberen waard als hard delen conflicten oplevert, en beide vallen buiten het bereik van de onderstaande meting.
Het experiment dat er toe doet: drie verliescompensatieschema's

Het naïeve verlies is schaalgevoelig. Als rendementsverlies leeft en volumeverlies rond , het volume bezit de gradiënt en de retourkop verhongert. Drie reacties:
Vaste gewichten. Set na het standaardiseren van elk doel. De eerlijke basislijn: als deze wint, zijn de adaptieve schema's een ceremonie.
Onzekerheidsweging (Kendall et al., 2018). Leer een homoscedastische ruisschaal per taak:
Taken met een hoge onzekerheid krijgen automatisch een lager gewicht; de term blokkeert het triviale oplossing. Merk dit op is een apparaat dat het verlies weegt tijdens de training, en niet een voorspellend interval. Voor onzekerheid waarmee u daadwerkelijk een positie kunt inschatten, zie conformal forecast.
GradNorm (Chen et al., 2018). Balansgradiënt magnitudes in plaats van verliesschalen. Elke stap: berekenen en het gemiddelde , bereken het relatieve trainingspercentage en bijwerken . Alle taken trainen vervolgens met vergelijkbare tarieven, ongeacht de verliesschaal.
De MTL-specifieke code is de heads, de lijst die naar voren terugkeert en de verliesaggregatie. De Linear/BatchNorm/ReLU/Dropout-stack, de Adam/cosine/clip boilerplate en de epoch-lus zijn het standaardpatroon dat wordt weergegeven in DeepLOB en zijn hier weggelaten.
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 heeft parameters, dus het moet naast het model in de optimizer gaan: optim.Adam(list(model.parameters()) + list(uw.parameters()), ...). Dit vergeten is de meest gebruikelijke manier om "onzekerheidsweging uit te voeren" en in plaats daarvan stilletjes vaste gewichten uit te voeren.
Wat te melden
Voor elk schema, op elke vouw: de geleerde uiteindelijke taakgewichten, de metriek voor de primaire taak, en – omdat een wegingsschema een modelkeuze is – hoeveel schema’s werden vergeleken voordat er één werd gekozen.
| Schema | Statistiek voor primaire taak versus enkele taak | |||
|---|---|---|---|---|
| Vast () | 1,00 | 1,00 | 1,00 | — |
| Onzekerheidsweging | — | — | — | — |
| GradNorm | — | — | — | — |
Drie schema's maal meerdere vouwen is al een kleine zoektocht naar modellen. Elke hier gerapporteerde verbetering moet de meervoudige testcorrectie overleven die wordt beschreven in leeggelopen Sharpe en meervoudig testen voordat het iets betekent.
Negatieve overdracht: onderteken de verlopen

Dit is het onderdeel dat de moeite waard is om te behouden. Negatieve overdracht is wanneer hulptaken de primaire taak verslechteren, en er is sprake van een directe diagnose: de hoek tussen taakgradiënten in de gedeelde parameterruimte.
Alleen gemeten op de gedeelde encoder: de koppen zijn taakspecifiek van constructie en zijn het altijd triviaal 'eens'.
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:]
}
Roep dit op tijdens een uitgestelde batch met een vaste cadans tijdens de training, en niet één keer aan het einde. Een paar kan beginnen uitgelijnd te worden en uiteen te lopen naarmate de encoder zich specialiseert; een enkel einde-trainingsnummer verbergt dat.
De bevinding om naar te zoeken – en hoe dan ook te publiceren:
| Paar | cos sim, vroege training | cos sim, late training | MTL geholpen bij primaire taak? |
|---|---|---|---|
| retour ↔ volume | — | — | — |
| rendement ↔ volatiliteit | — | — | — |
| volume ↔ volatiliteit | — | — | — |
Als volume- en volatiliteitsgradiënten met elkaar overeenkomen terwijl beide in strijd zijn met de retourgradiënt, is de juiste conclusie dat de twee hulptaken een samenhangend blok vormen waartoe de retourtaak niet behoort – en de oplossing is taakgroepering, niet meer capaciteit. Wanneer conflicten reëel zijn, zijn de standaardremedies PCGrad (Yu et al., 2020), waarbij elke conflicterende gradiënt op het normale vlak van de andere wordt geprojecteerd; CAGrad (Liu et al., 2021), dat zoekt naar een afdalingsrichting die geen enkele taak schaadt; of het geheel laten vallen van de hulptaak.
Merk op wat opzettelijk ontbreekt: een t-SNE-grafiek van de gedeelde representatie gekleurd door de doelwaarde. Het is decoratief: de cosinusgetallen hierboven zeggen alles waar de inbedding naar zou verwijzen, en ze zeggen het als getallen.
Validatieprotocol

De bovenstaande meting is waardeloos onder een slordig protocol, en MTL maakt de gebruikelijke vallen erger omdat er drie doelen moeten lekken in plaats van één.
Echte gegevens, geen simulator. De doelstellingen moeten afkomstig zijn van daadwerkelijke OHLCV-/handelsgegevens. Een hardgecodeerd GARCH-speelgoed genereert volatiliteit die gecorreleerd is met rendementen door constructie, wat precies het ding is dat wordt getest: het experiment zou zijn eigen generator meten. Als je een passend volatiliteitsproces wilt, past GARCH volatiliteitsvoorspelling voor crypto GARCH(1,1) aan met maximale waarschijnlijkheid op echte BTC/ETH en valideert de gestandaardiseerde residuen, en asymmetrische GARCH en het hefboomeffect beschrijft waarom een Gaussiaanse symmetrische responssimulator een verkeerde voorstelling geeft cryptovolatiliteit in de eerste plaats. Synthetische gegevens zijn alleen verdedigbaar als ze gecontroleerde grondwaarheden bieden – een bekende, door de auteur vastgestelde taakcorrelatie die je probeert te herstellen – wat een ander experiment is dan het experiment hier.
Scalers passen alleen op de trein. Plaats de functiescaler en alle drie de doelscalers in elke trainingsvouw en pas deze toe voor validatie; een mondiaal fit_transform Voordat u lekken opsplitst, test u momenten in de training. Deze exacte mislukking is gecatalogiseerd in de look-ahead bias-taxonomie.
Opgeschoond, onder embargo geplaatste walk-forward-folds. Een chronologische splitsing van 80/20 kan een MTL-verbetering niet onderscheiden van een fold-effect — dat is het hele argument van walk-forward-optimalisatie, dat drie splitsingen laat zien die drie conclusies opleveren. Hergebruik het uitvouwbare venster purged_walk_forward generator van spread modeling with machine learning: het laat een kloof van horizon rijen aan beide zijden van elke grens, wat hier van belang is omdat overlappende vensters van gerealiseerde volatiliteit over de grens lekken, zelfs als het rendementsdoel dat niet doet.
Een klassieke basislijn. Een MTL-net dat drie netten met één taak verslaat, heeft niets bewezen als een gradiëntverhogend of nokmodel per doel alle vier verslaat. Plaats één model per doel met LightGBM of nok op dezelfde vouwen en dezelfde kenmerken, en rapporteer dit in dezelfde tabel.
| Model | Metriek voor primaire taak | Opmerkingen |
|---|---|---|
| Rand, per doel | — | Klassieke basislijn |
| LightGBM, per doel | — | Klassieke basislijn |
| MLP voor één taak, per doel | — | Drie aparte netten |
| MTL, beste verliesregeling | — | Eén net, drie koppen |
Wat zou MTL hier de moeite waard maken

Voorwaarden waaronder MTL zou moeten winnen, vermeld als hypothesen om te vergelijken met de bovenstaande vouwen in plaats van als een checklist:
- Hulplabels zijn schoner dan het primaire label. Het volume wordt direct waargenomen; "verwacht rendement" is dat niet. Als de retourkop voornamelijk ruis bevat, is het gradiëntsignaal van de hulpkoppen het enige goed gepositioneerde deel van het objectief.
- Trainingsgegevens zijn beperkt in verhouding tot de capaciteit van de encoder, dus de aanvullende beperking zorgt voor echte regularisatie in plaats van alleen maar te strijden om parameters.
- Inferentielatentie is belangrijk en één voorwaartse pass is beter dan drie.
En het argument tegen, even toetsbaar: als het gemeten wordt cos_sim(return, ·) waarden zijn voortdurend negatief, de gedeelde encoder wordt weggetrokken van de primaire taak en de hulpkoppen zijn een belasting, geen regularizer.
Conclusie

Rendementen, volume en volatiliteit komen voort uit dezelfde microstructuur, dus een gedeelde representatie is een redelijke prior, maar een prior is geen resultaat. De twee dingen die deze opstelling feitelijk kan vaststellen zijn welk verliesbalanceringsschema de data prefereren (waarbij de geleerde gewichten worden gerapporteerd, niet alleen de naam van de winnaar) en of de taakgradiënten op de gedeelde encoder overeenkomen, gemeten tijdens training in plaats van te veronderstellen op basis van het feit dat de doelen gecorreleerd zijn.
Als uit de verwijderde walk-forward folds blijkt dat het MTL-net er niet in slaagt een gradiënt-verhogend model per doel te verslaan, dan is dat de bevinding en wordt deze als zodanig gepubliceerd: het sjabloon is het eerlijke negatieve. Een negatief resultaat op negatieve overdracht is nog steeds een resultaat op negatieve overdracht.
Auteurs
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.