← Quay lại danh sách bài viết
August 15, 2026
5 phút đọc

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) thường được quảng bá bằng một khẳng định: chia sẻ encoder giữa các target tương quan và task chính sẽ tốt hơn. Trong trading, các target tương quan rất rõ — return, volume và realized volatility đều xuất phát từ cùng order flow — và khẳng định này gần như chưa bao giờ được kiểm tra. Câu hỏi thú vị không phải là các task có liên quan hay không. Mà là các gradient được chia sẻ có đồng thuận hay không, và điều gì xảy ra ở các fold nơi chúng không đồng thuận.

Bài viết này đặt hai điều ở trung tâm, trong khi hầu hết các bài viết về MTL chỉ coi chúng là chú thích:

  1. Cân bằng loss là experiment, không phải chi tiết. Fixed weights, Kendall uncertainty weighting và GradNorm là ba model khác nhau. Chạy cả ba trên cùng các fold và báo cáo learned weights cùng metric của task chính cho từng model.
  2. Có thể đo negative transfer trước khi thấy metric. Cosine similarity giữa các gradient task trên shared encoder cho biết trong quá trình training liệu các task phụ có kéo representation về một hướng mà task chính không muốn đi hay không. Ghi dấu các cosine, rồi kiểm tra xem dấu đó có dự đoán outcome ở fold tương ứng hay không.

Mọi thứ khác trong pipeline — volatility process, training loop, leakage controls, validation protocol — đã được đề cập ở nơi khác trên blog này và được dẫn link thay vì dẫn xuất lại.

Thiết lập

Ba target thị trường xuất hiện từ một latent representation được chia sẻ

Với các input feature xRd\mathbf{x} \in \mathbb{R}^d (OHLCV, technical indicators, order flow), có ba target:

  • Task 1 (chính): return kỳ tiếp theo y(1)=rt+1y^{(1)} = r_{t+1}
  • Task 2 (phụ): log volume kỳ tiếp theo y(2)=logVt+1y^{(2)} = \log V_{t+1}
  • Task 3 (phụ): realized volatility kỳ tiếp theo y(3)=σt+1y^{(3)} = \sigma_{t+1}

Model đa nhiệm tạo ra cả ba đồng thời, y^=fθ(x)\hat{\mathbf{y}} = f_\theta(\mathbf{x}), và rủi ro đa nhiệm là tổng có trọng số của rủi ro từng task:

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]

Toàn bộ bài viết nói về wkw_k và cách gradient của từng task tác động lẫn nhau.

Vì sao training chung có thể hữu ích, trong một đoạn. Các task phụ buộc representation được chia sẻ phải giải thích nhiều hơn một hiện tượng thị trường, vừa là kiểm soát capacity vừa là inductive bias; hơn nữa volume và volatility được quan sát trực tiếp, còn "expected return" thì không, nên các head phụ cung cấp gradient signal sạch hơn head chính. Lập luận cho một model phát ra nhiều output được trình bày dài — kèm cơ chế interpretability — trong temporal fusion transformers cho dự báo nhiều chân trời, nơi đưa ra cùng lập luận shared-encoder-many-heads cho các quantile nhiều chân trời.

Kiến trúc, nói ngắn gọn

Hard parameter sharing: encoder dùng chung gϕg_\phi cấp dữ liệu cho KK head riêng theo task hψkh_{\psi_k}, nên y^(k)=hψk(gϕ(x))\hat{y}^{(k)} = h_{\psi_k}(g_\phi(\mathbf{x})). Đây là phiên bản được đo ở đây, vì đây là phiên bản mà gradient conflict trên ϕ\phi được định nghĩa rõ.

Soft parameter sharing cho mỗi task một encoder riêng với coupling penalty λkjϕkϕj2\lambda \sum_{k \neq j} \|\phi_k - \phi_j\|^2 — nhiều parameter hơn, linh hoạt hơn và không có một vector parameter dùng chung duy nhất để đo conflict. Cross-stitch networks nằm ở giữa, trộn feature của từng task qua learned matrix αkj\alpha_{kj} ở mỗi level. Cả hai đều đáng thử nếu hard sharing cho thấy conflict, và cả hai nằm ngoài phạm vi phép đo bên dưới.

Experiment quan trọng: Ba scheme cân bằng loss

Các gradient task cạnh tranh được cân bằng quanh neural core dùng chung

Loss ngây thơ L=kwkL(k)\mathcal{L} = \sum_k w_k \mathcal{L}^{(k)} nhạy với scale. Nếu loss return ở khoảng 0.010.01 còn loss volume ở khoảng 1.01.0, volume sẽ sở hữu gradient và head return bị bỏ đói. Có ba cách đáp ứng:

Fixed weights. Đặt wk=1w_k = 1 sau khi standardize mọi target. Đây là baseline trung thực — nếu nó thắng, các scheme adaptive chỉ là nghi lễ.

Uncertainty weighting (Kendall et al., 2018). Học một homoscedastic noise scale σk\sigma_k cho từng task:

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

Các task có độ bất định cao tự động nhận trọng số thấp hơn; hạng logσk\log \sigma_k chặn nghiệm tầm thường σk\sigma_k \to \infty. Lưu ý σk\sigma_k này là cơ chế weighting loss trong lúc training, không phải predictive interval — về độ bất định có thể dùng để định cỡ position, xem conformal prediction.

GradNorm (Chen et al., 2018). Cân bằng độ lớn gradient thay vì scale của loss. Mỗi step: tính Gk=ϕwkL(k)2G_k = \|\nabla_\phi w_k \mathcal{L}^{(k)}\|_2 và mean Gˉ\bar{G}, tính relative training rate r~k=L(k)(t)/L(k)(0)rˉ\tilde{r}_k = \frac{\mathcal{L}^{(k)}(t)/\mathcal{L}^{(k)}(0)}{\bar{r}}, rồi cập nhật 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|. Khi đó mọi task training ở tốc độ tương đương bất kể scale loss.

Code đặc thù MTL là các head, forward trả về list và việc gộp loss. Stack Linear/BatchNorm/ReLU/Dropout, boilerplate Adam/cosine/clip và epoch loop là pattern tiêu chuẩn được trình bày trong DeepLOB và được lược bỏ ở đây.

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 có parameter, vì vậy nó phải được đưa vào optimizer cùng model: optim.Adam(list(model.parameters()) + list(uw.parameters()), ...). Quên điều này là cách phổ biến nhất để "chạy uncertainty weighting" nhưng âm thầm chạy fixed weights.

Cần báo cáo gì

Với mỗi scheme, trên mỗi fold: task weights cuối cùng đã học, metric của task chính và — vì scheme weighting là một lựa chọn model — có bao nhiêu scheme đã được so sánh trước khi chọn một scheme.

Scheme wreturnw_{\text{return}} wvolumew_{\text{volume}} wvolw_{\text{vol}} Primary-task metric vs single-task
Fixed (wk=1w_k = 1) 1.00 1.00 1.00
Uncertainty weighting
GradNorm

Ba scheme nhân với vài fold đã là một model search nhỏ. Bất kỳ improvement nào báo cáo ở đây phải vượt qua multiple-testing correction được mô tả trong deflated Sharpe và multiple testing trước khi có ý nghĩa.

Negative Transfer: Ghi dấu các gradient

Các gradient task được căn chỉnh và xung đột tại representation dùng chung

Đây là phần đáng giữ lại. Negative transfer xảy ra khi các task phụ làm task chính tệ hơn, và nó có một diagnostic trực tiếp: góc giữa các gradient task trong không gian parameter dùng chung.

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}

Chỉ đo trên shared encoder — các head vốn riêng theo task và luôn "đồng thuận" một cách tầm thường.

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:]
    }

Gọi hàm này trên một batch held-out theo cadence cố định trong quá trình training, không chỉ một lần ở cuối. Một cặp có thể bắt đầu aligned rồi diverge khi encoder specialize; một con số duy nhất ở cuối training sẽ che giấu điều đó.

Điều cần tìm — và dù theo hướng nào cũng đáng công bố:

Pair cos sim, early training cos sim, late training MTL helped primary task?
return ↔ volume
return ↔ volatility
volume ↔ volatility

Nếu gradient volume và volatility đồng thuận với nhau nhưng cả hai xung đột với gradient return, kết luận đúng là hai task phụ tạo thành một block nhất quán mà task return không thuộc về — và cách sửa là group task, không phải tăng capacity. Khi conflict là thật, các remedy tiêu chuẩn là PCGrad (Yu et al., 2020), chiếu từng gradient xung đột lên mặt phẳng pháp tuyến của gradient kia; CAGrad (Liu et al., 2021), tìm hướng đi xuống không làm hại task nào; hoặc loại bỏ hoàn toàn task phụ.

Lưu ý điều cố ý vắng mặt: một plot t-SNE của representation dùng chung được tô màu theo giá trị target. Nó chỉ mang tính trang trí — các con số cosine ở trên đã nói tất cả những gì embedding định gợi ý, và chúng nói bằng số.

Protocol validation

Các cửa sổ validation walk-forward tách biệt trên timeline nghiên cứu

Phép đo bên trên vô giá trị nếu protocol cẩu thả, và MTL làm các bẫy thường gặp tệ hơn vì có ba target để leak thay vì một.

Dữ liệu thực, không phải simulator. Các target phải đến từ dữ liệu OHLCV/trade thực tế. Một GARCH toy hardcoded tạo volatility tương quan với return do cách xây dựng, chính là điều đang được kiểm tra — experiment sẽ đo chính generator của nó. Nếu muốn volatility process đã fit, dự báo volatility crypto bằng GARCH fit GARCH(1,1) bằng maximum likelihood trên BTC/ETH thực và validate standardized residuals, còn GARCH bất đối xứng và hiệu ứng leverage giải thích vì sao simulator Gaussian có phản ứng đối xứng ngay từ đầu đã mô tả sai volatility crypto. Synthetic data chỉ có thể biện hộ khi cung cấp controlled ground truth — một task correlation đã biết do tác giả đặt ra mà bạn muốn khôi phục — đây là experiment khác với experiment ở đây.

Chỉ fit scaler trên train. Fit feature scaler và cả ba target scaler bên trong từng training fold rồi áp dụng vào validation; một fit_transform toàn cục trước khi split sẽ leak moments của test set vào training. Lỗi chính xác này được liệt kê trong taxonomy của look-ahead bias.

Các fold walk-forward đã purge và embargo. Một split theo thời gian 80/20 không thể phân biệt MTL improvement với fold effect — đó là toàn bộ lập luận của walk-forward optimization, nơi ba split tạo ra ba kết luận. Tái sử dụng generator expanding-window purged_walk_forward từ mô hình hóa spread bằng machine learning: nó bỏ ra một khoảng cách gồm horizon dòng ở cả hai phía của mỗi boundary, điều quan trọng ở đây vì các cửa sổ realized-volatility chồng lấn sẽ leak qua boundary ngay cả khi target return không leak.

Một baseline cổ điển. Một MTL net đánh bại ba single-task net chưa chứng minh gì nếu một model gradient-boosting hoặc ridge theo từng target đánh bại cả bốn. Fit một model cho mỗi target bằng LightGBM hoặc ridge trên cùng fold và feature, rồi báo cáo trong cùng bảng.

Model Primary-task metric Notes
Ridge, per target Classical baseline
LightGBM, per target Classical baseline
Single-task MLP, per target Three separate nets
MTL, best loss scheme One net, three heads

Điều gì khiến MTL đáng dùng ở đây

Ngưỡng quyết định được đo cho độ phức tạp của model đa nhiệm

Các điều kiện mà MTL nên thắng, được nêu như hypotheses cần kiểm tra trên các fold ở trên chứ không phải checklist:

  • Nhãn phụ sạch hơn nhãn chính. Volume được quan sát trực tiếp; "expected return" thì không. Nếu head return chủ yếu fit noise, gradient signal từ các head phụ là phần duy nhất được đặt đúng của objective.
  • Dữ liệu training hạn chế so với capacity của encoder, nên ràng buộc phụ thực sự regularize thay vì chỉ cạnh tranh parameter.
  • Inference latency quan trọng và một forward pass tốt hơn ba.

Và lập luận ngược lại cũng có thể kiểm tra: nếu các giá trị cos_sim(return, ·) đo được liên tục âm, encoder dùng chung đang bị kéo ra khỏi task chính và các head phụ là một khoản thuế, không phải regularizer.

Kết luận

Các luồng dự báo hài hòa kết tụ thành một kết luận đa nhiệm

Return, volume và volatility đến từ cùng microstructure, nên representation dùng chung là một prior hợp lý — nhưng prior không phải kết quả. Thiết lập này thực sự có thể xác lập hai điều: dữ liệu ưa scheme cân bằng loss nào (với learned weights được báo cáo, không chỉ gọi tên winner) và gradient task trên encoder dùng chung có đồng thuận hay không, được đo trong quá trình training thay vì giả định chỉ vì các target tương quan.

Nếu các fold walk-forward đã purge cho thấy MTL net không đánh bại model gradient-boosting theo target, đó là finding và sẽ được công bố đúng như vậy — template là kết quả phủ định trung thực. Một kết quả phủ định về negative transfer vẫn là một kết quả về negative transfer.

Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

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

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.