← Terug naar artikelen
August 15, 2026
5 min leestijd

Multi-Task Learning for Simultaneous Price, Volume, and Volatility Prediction

Multi-Task Learning for Simultaneous Price, Volume, and Volatility Prediction
#deep-learning
#multi-task
#shared-representation
#prediction
#auxiliary

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:

  1. 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.
  2. 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

Drie marktdoelen die voortkomen uit een gedeelde latente representatie

Gegeven invoerfuncties xRd\mathbf{x} \in \mathbb{R}^d (OHLCV, technische indicatoren, orderstroom), drie doelstellingen:

  • Taak 1 (primair): rendement volgende periode y(1)=rt+1y^{(1)} = r_{t+1}
  • Taak 2 (hulp): logvolume voor de volgende periode y(2)=logVt+1y^{(2)} = \log V_{t+1}
  • Taak 3 (hulp): gerealiseerde volatiliteit in de volgende periode y(3)=σt+1y^{(3)} = \sigma_{t+1}

Een multi-taskmodel produceert ze alle drie tegelijk, y^=fθ(x)\hat{\mathbf{y}} = f_\theta(\mathbf{x}), en het multitaskrisico is een gewogen som van de risico's per taak:

RMTL(θ)=k=1KwkE[(k)(fθ(k)(x),y(k))]\mathcal{R}_{\text{MTL}}(\theta) = \sum_{k=1}^{K} w_k \cdot \mathbb{E}\bigl[\ell^{(k)}(f_\theta^{(k)}(\mathbf{x}), y^{(k)})\bigr]

Het hele artikel gaat over de wkw_k 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 gϕg_\phi feeds KK taakspecifieke hoofden hψkh_{\psi_k}, Dus y^(k)=hψk(gϕ(x))\hat{y}^{(k)} = h_{\psi_k}(g_\phi(\mathbf{x})). Dit is de hier gemeten versie, omdat het de versie is waar gradiëntconflicten optreden ϕ\phi is goed gedefinieerd.

Zacht delen van parameters geeft elke taak een eigen encoder met een koppelingsstraf λkjϕkϕj2\lambda \sum_{k \neq j} \|\phi_k - \phi_j\|^2 – 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 αkj\alpha_{kj} 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

Concurrerende taakgradiënten gebalanceerd rond een gedeelde neurale kern

Het naïeve verlies L=kwkL(k)\mathcal{L} = \sum_k w_k \mathcal{L}^{(k)} is schaalgevoelig. Als rendementsverlies leeft 0.010.01 en volumeverlies rond 1.01.0, het volume bezit de gradiënt en de retourkop verhongert. Drie reacties:

Vaste gewichten. Set wk=1w_k = 1 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 σk\sigma_k per taak:

LMTL=k=1K12σk2L(k)+logσk\mathcal{L}_{\text{MTL}} = \sum_{k=1}^{K} \frac{1}{2\sigma_k^2} \mathcal{L}^{(k)} + \log \sigma_k

Taken met een hoge onzekerheid krijgen automatisch een lager gewicht; de logσk\log \sigma_k term blokkeert het triviale σk\sigma_k \to \infty oplossing. Merk dit op σk\sigma_k 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 Gk=ϕwkL(k)2G_k = \|\nabla_\phi w_k \mathcal{L}^{(k)}\|_2 en het gemiddelde Gˉ\bar{G}, bereken het relatieve trainingspercentage r~k=L(k)(t)/L(k)(0)rˉ\tilde{r}_k = \frac{\mathcal{L}^{(k)}(t)/\mathcal{L}^{(k)}(0)}{\bar{r}}en bijwerken wkwkηwwkkGkGˉr~kαw_k \leftarrow w_k - \eta_w \nabla_{w_k} \sum_k |G_k - \bar{G} \cdot \tilde{r}_k^\alpha|. 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 wreturnw_{\text{return}} wvolumew_{\text{volume}} wvolw_{\text{vol}} Statistiek voor primaire taak versus enkele taak
Vast (wk=1w_k = 1) 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

Uitgelijnde en conflicterende taakgradiënten bij een gedeelde representatie

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.

cos(ϕL(k),ϕL(j))<0    conflicting tasks\cos\bigl(\nabla_\phi \mathcal{L}^{(k)}, \nabla_\phi \mathcal{L}^{(j)}\bigr) < 0 \implies \text{conflicting tasks}

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

Gescheiden validatievensters voorwaarts over een onderzoekstijdlijn

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

Een gemeten beslissingsdrempel voor de complexiteit van multi-task modellen

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

Geharmoniseerde voorspellingsstromen die worden omgezet in een conclusie voor meerdere taken

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.

Disclaimer: De informatie in dit artikel is uitsluitend bedoeld voor educatieve en informatieve doeleinden en vormt geen financieel, beleggings- of handelsadvies. Het handelen in cryptovaluta brengt een aanzienlijk risico op verlies met zich mee.

Auteurs

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Blijf de markt voor

Abonneer je op onze nieuwsbrief voor exclusieve AI-handelsinzichten, marktanalyses en platformupdates.

We respecteren je privacy. Je kunt je op elk moment afmelden.