Multi-Task Learning for Simultaneous Price, Volume, and Volatility Prediction
マルチタスク学習 (MTL) は通常、相関するターゲット間でエンコーダーを共有すると、主要なタスクが向上するという主張とともに販売されます。取引では、相関する目標は明らかです。リターン、出来高、実現ボラティリティはすべて同じ注文フローから外れます。そして、その主張がテストされることはほとんどありません。興味深いのは、タスクが関連しているかどうかではありません。それは、共有されたグラデーションが「一致」するかどうか、そして一致しない折り目で何が起こるかです。
この記事では、ほとんどの MTL 記事が脚注として扱っている 2 つのことを中心に据えています。
- 損失バランスは実験であり、詳細ではありません。 固定重み、Kendall 不確実性重み付け、および GradNorm は 3 つの異なるモデルです。 3 つすべてを同じフォールドで実行し、学習した重みをそれぞれのプライマリ タスク メトリックとともにレポートします。
- 負の伝達は、メトリクスを確認する前に測定可能です。 共有エンコーダー上のタスク勾配間のコサイン類似度により、トレーニング中に、補助タスクが主タスクが行きたい場所に表現を引っ張っているかどうかがわかります。コサインに署名し、その符号がその折り目の結果を予測したかどうかを確認します。
パイプライン内の他のすべてのもの (ボラティリティ プロセス、トレーニング ループ、リーク コントロール、検証プロトコル) は、このブログの他の場所ですでに説明されており、再派生ではなくリンクされています。
## 設定

与えられた入力特徴 (OHLCV、テクニカル指標、注文フロー)、3 つのターゲット:
- タスク 1 (主): 次の期間の戻り値
- タスク 2 (補助): 次の期間のログ ボリューム
- タスク 3 (補助): 次の期間の実現ボラティリティ
マルチタスク モデルは、3 つすべてを同時に生成します ()。マルチタスク リスクは、タスクごとのリスクの加重合計です。
この記事全体は、 と、タスクごとの勾配が相互にどのような影響を与えるかについて説明しています。
共同トレーニングが役立つ理由を 1 つの段落で説明します。 補助タスクは、複数の市場現象を説明するために共有表現を制約します。これは、キャパシティ制御であると同時に帰納的バイアスでもあります。また、ボリュームとボラティリティは直接観察されますが、「期待リターン」は観察されないため、補助ヘッドは主ヘッドよりもクリーンな勾配信号を供給します。多くの出力を発行する 1 つのモデルのケースは、解釈機構を付加して マルチホライズン予測のための時間融合トランスフォーマー] で詳細に議論されており、マルチホライズン分位数に対して同じ共有エンコーダー多ヘッドの議論が行われます。
アーキテクチャの概要
ハード パラメーター共有: 共有エンコーダー は、 タスク固有のヘッド にフィードします。つまり、 になります。これは、 での勾配の競合が明確に定義されているバージョンであるため、ここで測定したバージョンです。
ソフト パラメーター共有 では、各タスクにカップリング ペナルティ を持つ独自のエンコーダーが与えられます。パラメーターが増え、柔軟性が向上し、競合を測定する単一の共有パラメーター ベクトルがなくなりました。 クロスステッチ ネットワークはその中間に位置し、各レベルで学習されたマトリックス を通じてタスクごとの機能を混合します。ハード共有で矛盾が見られる場合は、両方とも試してみる価値がありますが、両方とも以下の測定の範囲外です。
重要な実験: 3つの損失バランス手法

単純損失 はスケールに依存します。リターンロスが 付近にあり、ボリューム損失が 付近にある場合、ボリュームが勾配を所有し、リターン ヘッドが不足します。 3 つの応答:
固定重み。 すべてのターゲットを標準化した後、 を設定します。正直なベースライン — それが勝てば、適応スキームは儀式のようになります。
不確実性の重み付け (Kendall et al.、2018)。 タスクごとの等分散性ノイズ スケール を学習します。
不確実性の高いタスクは自動的に重み付けされます。 用語は、簡単な ソリューションをブロックします。この は、予測間隔ではなく、トレーニング時の損失重み付けデバイスであることに注意してください。実際に位置のサイズを決定できる不確実性については、等角予測.
GradNorm (Chen et al., 2018). 損失スケールではなく勾配 大きさ のバランスをとります。各ステップ: と平均 を計算し、相対トレーニング率 を計算し、 を更新します。その後、損失規模に関係なく、すべてのタスクが同等のレートでトレーニングされます。
MTL 固有のコードは、ヘッド、リストリターンフォワード、および損失集計です。 Linear/BatchNorm/ReLU/Dropout スタック、Adam/cosine/clip ボイラープレート、およびエポック ループは、DeepLOB] に示されている標準パターンであるため、ここでは省略されています。
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 にはパラメーターがあるため、モデル optim.Adam(list(model.parameters()) + list(uw.parameters()), ...) と一緒にオプティマイザーに入力する必要があります。これを忘れて「不確実性重み付けを実行」し、代わりに固定重み付けを黙って実行する最も一般的な方法です。
報告する内容
各スキームの各フォールド: 学習された最終タスクの重み、プライマリ タスク メトリック、および重み付けスキームはモデルの選択であるため、スキームを選択する前に比較されたスキームの数。
| スキーム | プライマリタスクのメトリクスとシングルタスクの比較 | |||
|---|---|---|---|---|
| 修正済み () | 1.00 | 1.00 | 1.00 | — |
| 不確実性の重み付け | — | — | — | — |
| 卒業生 | — | — | — | — |
3 つのスキームを数倍に掛ければ、すでに小さなモデルの検索になります。ここで報告された改善は、意味を成す前に、収縮したシャープと複数のテスト] で説明されている複数のテストの修正を乗り越える必要があります。
負の転移: 勾配が示すもの

これは保存する価値のある部分です。負の伝達は、補助タスクによって主タスクが悪化する場合であり、共有パラメータ空間におけるタスク勾配間の角度という直接的な診断が可能です。
共有エンコーダのみで測定 - ヘッドは構造上タスク固有であり、常に自明に「同意」します。
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:]
}
これは、最後に 1 回ではなく、トレーニング中に一定のリズムで保持されたバッチに対して呼び出します。ペアは整列して開始され、エンコーダーが特殊化するにつれて分岐する可能性があります。単一のトレーニング終了番号がそれを隠します。
探して、いずれかの方法で公開する調査結果:
| ペア | cos sim、初期トレーニング | cos sim、後期トレーニング | MTL は主なタスクに役立ちましたか? |
|---|---|---|---|
| リターン ↔ ボリューム | — | — | — |
| リターン ↔ ボラティリティ | — | — | — |
| 出来高 ↔ ボラティリティ | — | — | — |
出来高とボラティリティの勾配が互いに一致し、両方ともリターンの勾配と矛盾する場合、正しい結論は、2 つの補助タスクがリターン タスクが属さない一貫したブロックを形成するということです。そして、解決策は容量の増加ではなく、タスクのグループ化です。矛盾が実際にある場合、標準的な救済策は PCGrad (Yu et al., 2020) です。これは、矛盾する各勾配を他の勾配の法線平面に投影します。 CAGrad (Liu et al., 2021)。タスクに悪影響を及ぼさない下降方向を検索します。または補助タスクを完全に削除します。
意図的に欠落しているものに注目してください。ターゲット値によって色付けされた共有表現の t-SNE プロットです。これは装飾的なものです。上のコサイン数値は、埋め込みがジェスチャーするすべてのことを数値として表しています。
検証プロトコル

上記の測定は、ずさんなプロトコルの下では役に立ちません。MTL では、リークするターゲットが 1 つではなく 3 つあるため、通常のトラップがさらに悪化します。
シミュレーターではなく実際のデータ。 ターゲットは実際の OHLCV/貿易データから取得する必要があります。ハードコードされた GARCH おもちゃは、建設によって収益と相関するボラティリティを生成します。これはまさにテスト対象のものであり、実験では独自のジェネレーターを測定することになります。ボラティリティの近似プロセスが必要な場合は、暗号の GARCH ボラティリティ予測 は、実際の BTC/ETH で最尤法により GARCH(1,1) を近似し、標準化された残差を検証します。また、非対称 GARCH とレバレッジ効果 は、そもそもガウス対称応答シミュレーターが暗号通貨のボラティリティを誤って表示する理由をカバーしています。合成データは、管理されたグラウンド トゥルース (回復しようとしている既知の作成者設定のタスク相関関係) を提供する場合にのみ防御可能です。これは、ここでの実験とは異なる実験です。
スケーラーはトレーニングのみに適合します。 特徴スケーラーと 3 つのターゲット スケーラーすべてを各トレーニング フォールド内に適合させ、検証に適用します。リークのテストセットの瞬間をトレーニングに分割する前に、グローバル fit_transform。この正確な失敗は、先読みバイアス分類法] にカタログ化されています。
パージされ、禁輸されたウォークフォワード フォールド。 1 つの 80/20 時系列分割では MTL 改善とフォールド効果を区別することはできません。これが ウォークフォワード最適化] の議論全体であり、3 つの分割が 3 つの結論を生み出すことを示しています。 機械学習を使用したスプレッドモデリング] の拡張ウィンドウ purged_walk_forward ジェネレーターを再利用します。これにより、各境界の両側に horizon 行のギャップが削除されます。これは、リターン ターゲットがそうでない場合でも、重複する実現ボラティリティ ウィンドウが境界を越えてリークするため、ここで重要です。
古典的なベースライン。 ターゲットごとの勾配ブースティング モデルまたはリッジ モデルが 4 つすべてを上回る場合、MTL ネットが 3 つのシングルタスク ネットに勝っても何も証明されません。 LightGBM またはリッジを使用してターゲットごとに 1 つのモデルを同じ折り目および同じフィーチャに適合させ、同じテーブルでレポートします。
| モデル | プライマリタスクのメトリクス | メモ |
|---|---|---|
| リッジ、ターゲットごと | — | 古典的なベースライン |
| LightGBM、ターゲットごと | — | 古典的なベースライン |
| シングルタスク MLP、ターゲットごと | — | 3 つの別々のネット |
| MTL、最良の損失スキーム | — | 1 つのネット、3 つのヘッド |
MTLに価値があるのはどんな場合か

MTL が勝つための条件。チェックリストではなく、上記のフォールドと照らし合わせてチェックするための仮説として述べられています。
- 補助ラベルは主ラベルよりもきれいです。ボリュームは直接観察されます。 「期待収益」はそうではありません。リターン ヘッドのほとんどがフィッティング ノイズである場合、補助ヘッドからの勾配信号が対物レンズの唯一適切に設定された部分になります。
- トレーニング データはエンコーダの容量に応じて制限されるため、補助制約はパラメータを競合するだけでなく、実際の正則化作業を実行します。
- 推論の遅延は重要であり、1 つの前方パスは 3 つよりも優れています。
反対の場合も同様にテスト可能です。測定された cos_sim(return, ·) 値が継続的に負である場合、共有エンコーダーは主要なタスクから引き離されており、補助ヘッドはレギュラライザーではなく税金です。
## 結論

リターン、出来高、ボラティリティは同じ微細構造から得られるため、共有表現は合理的な事前分布ですが、事前分布は結果ではありません。この設定で実際に確立できる 2 つのことは、データがどの損失バランス スキームを好むか (勝者が指定されただけでなく、学習された重みが報告される)、および共有エンコーダーのタスク勾配が一致するかどうか (ターゲットが相関しているという事実から推測されるのではなく、トレーニングを通じて測定される) です。
パージされたウォークフォワード フォールドによって、MTL ネットがターゲットごとの勾配ブースティング モデルに勝てないことが示された場合、それが発見であり、そのように公開されます。テンプレートは 正直な negative.負の転送に関する負の結果は、依然として負の転送に関する結果です。
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.