気温、湿度、気圧、風速 — 気象観測ステーションは毎秒このような多変量時系列データを記録し続けています。ある気象研究者が過去10年分のデータの中から「急激な気温低下を伴う寒冷前線の通過パターン」を探したいとします。従来の方法では、閾値ベースのルールを手作業で設計するか、統計的な異常検知を適用するかのどちらかでした。しかし、もしテキストで「寒冷前線の通過:気温の急降下、気圧の急上昇、風向の急変を伴う」とクエリを入力するだけで、該当する時系列区間が検索結果として返ってきたらどうでしょうか。
この「時系列データをテキストで検索する」という発想は、画像とテキストの対照学習で大きな成功を収めた CLIP の考え方を、時系列データに拡張するものです。しかし、時系列データには画像にはない固有の難しさがあります。多変量時系列の各チャネルは異なる物理量(気温、湿度、気圧など)を表しており、チャネルごとに意味が異なります。さらに、同じ数値パターンでも文脈によって意味がまったく異なる場合があります。
TRACE(Chen et al., NeurIPS 2025)は、この課題に正面から取り組んだフレームワークです。時系列データとテキストを共通の埋め込み空間にマッピングし、サンプルレベルとチャネルレベルの両方で対照学習を行うことで、高精度なマルチモーダル検索を実現しました。
TRACEの理解は、以下のような応用領域で直接的に役立ちます。
- 医療: 患者のバイタルサイン(心拍数、血圧、体温、SpO2)の時系列データに対して、「敗血症の初期徴候:心拍数の上昇と血圧の低下が同時に見られるパターン」のようなテキストクエリで類似症例を検索できます
- 気象・気候科学: 観測データに対して自然言語で気象現象を記述し、該当する時系列区間を検索できます。これは過去の極端気象イベントの分析や、気候パターンの同定に応用されます
- エネルギー・インフラ監視: 発電設備のセンサデータに対して「タービンの異常振動パターン」のようなテキストクエリで異常区間を検索し、予防保全に活用できます
- 金融: 複数の経済指標の時系列データから、テキストで記述されたマクロ経済パターンに合致する期間を検索できます
本記事の内容
- TRACEが解決する課題 — 時系列データのセマンティック接地
- Channel Identity Token (CIT) の設計思想と理論
- Channel-Biased Attention (CbA) の数理
- 2段階学習(MAE事前学習 + マルチモーダルアライメント)の全体像
- Hard Negative Miningの戦略
- 双方向InfoNCE損失の定式化
- PyTorchによる簡易版TRACEの実装
- 実験結果の解説とアブレーション分析
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
TRACEの全体像 — 時系列データをテキストで「意味づけ」する
TRACEが取り組む課題を端的に述べると、時系列データにセマンティック(意味的)な接地(grounding)を与えるということです。
画像認識の世界では、CLIPによって「画像をテキストと同じ埋め込み空間に配置する」ことが実現され、「犬の画像」と「a photo of a dog」というテキストが近い位置にマッピングされます。これにより、ゼロショット分類やテキストによる画像検索が可能になりました。
しかし、時系列データではこのアプローチをそのまま適用できません。その理由は大きく2つあります。
第一に、時系列データはモダリティとしての抽象度が高いことです。画像には「犬」「猫」「車」のような視覚的に明確なオブジェクトが含まれていますが、時系列データの数値パターンは人間が直感的に意味を読み取ることが難しく、テキストとの対応付けが自然には生まれません。
第二に、多変量時系列にはチャネルの構造があることです。画像のRGBチャネルとは異なり、時系列データの各チャネルは「気温」「湿度」「気圧」のように、それぞれ独立した物理量を表しています。チャネルごとに異なるテキスト記述が対応する必要があるため、サンプル全体をひとつのベクトルに圧縮するだけでは情報が失われてしまいます。
TRACE (Chen et al., 2025) は、これらの課題を解決するために以下の設計を採用しています。
- 2段階学習: まず時系列の表現を自己教師あり学習(Masked Autoencoder)で獲得し、次にテキストとのアライメントを行います
- Channel Identity Token (CIT): 各チャネルに固有のトークンを付与し、チャネルごとの意味的な表現を保持します
- Channel-Biased Attention (CbA): チャネル間の意味的混在を防ぐ制約付きアテンション機構です
- サンプルレベル + チャネルレベルの対照学習: 粗い粒度と細かい粒度の両方でテキストとのアライメントを行います

この図がTRACEの全体像です。左側の多変量時系列(気温・湿度・気圧)と、右側の自然言語による記述を、それぞれエンコーダで中央の共有埋め込み空間に写します。意味的に対応する時系列とテキストが空間上で近くに配置されるよう学習することで、「テキストで時系列を検索する」「時系列からテキストを引き当てる」という双方向の検索が可能になります。CLIPが画像とテキストで行ったことを、構造を持つ多変量時系列に対して実現するのがTRACEだと捉えてください。
論文では、この発想を次のような具体的なユースケースで示しています。

出典: Chen et al., “TRACE: Grounding Time Series in Context for Multimodal Embedding and Retrieval”, NeurIPS 2025, Figure 1.
上は論文Figure 1の例です。「鉄砲水イベント報告(Flash Flood Event Report)」というテキスト記述をクエリにすると、TRACEがそれに合致する7チャネルの時系列(気温・降水量・相対湿度・視程・東西風・南北風・雲量)を実際に検索して返しています。テキストの色付き箇所(降水のスパイク、湿度の高止まり、視程の低下など)が、検索された各チャネルの波形と対応している点に注目してください。記事冒頭で述べた「テキストで時系列を探す」が、まさにこの形で実現されます。
この全体像を頭に入れた上で、まずは既存手法がなぜうまくいかないのかを具体的に見ていきましょう。
既存手法の課題 — なぜ単純なCLIP拡張ではダメなのか
時系列データとテキストのマルチモーダル学習は、TRACEが登場する以前にも試みられていました。しかし、既存の手法にはいくつかの根本的な限界がありました。
セマンティック接地の欠如
多くの時系列基盤モデル(foundation models)は、数値予測(forecasting)やパターン分類(classification)には優れていますが、「この時系列が何を意味するか」というセマンティックな理解を持っていません。例えば、ある時系列エンコーダが気温データの急降下パターンを数値的に捉えていたとしても、それが「寒冷前線の通過」を意味するという知識は、テキストとの対応付けなしには獲得できません。
Chen et al. (2025) はこの問題を 「セマンティック接地の欠如(lack of semantic grounding)」 と呼んでいます。時系列データが持つ数値パターンの意味を、人間が理解可能な自然言語と結びつけることが必要なのです。
チャネル独立処理 vs. チャネル混合処理
多変量時系列を扱う従来のアプローチは、大きく2つに分かれます。
チャネル独立(Channel-Independent)型は、各チャネルを独立に処理します。これはチャネル間の相互作用を無視してしまうため、「気温が下がると同時に湿度が上がる」のようなチャネル間の関係を捉えられません。
チャネル混合(Channel-Mixing)型は、全チャネルをまとめて処理します。しかし、異なる物理量のチャネルを区別なく混合すると、各チャネルの固有の意味が曖昧になります。例えば、「気温チャネルについてのテキスト」と「湿度チャネルについてのテキスト」を個別に対応付けることが困難になります。
TRACEは、この二者択一を超えるアプローチとして Channel Identity Token (CIT) と Channel-Biased Attention (CbA) を提案しています。これにより、チャネルごとの独立した表現を維持しながら、必要に応じてチャネル間の情報交換も可能にしています。

3つの方式を並べると違いが明確になります。チャネル独立(左)は各チャネルを別々に処理するため、気温と気圧の同時変化のようなチャネル間の関係を取りこぼします。チャネル混合(中央)は全チャネルを1つの表現に押し込むため、どこが気温の意味でどこが湿度の意味なのかが曖昧になります。TRACE(右)は各チャネルに代表トークン CIT を立て、CbA で選択的に連携させることで、チャネルごとの純粋な意味を保ちながらチャネル間の相互作用も学習できる、という「いいとこ取り」を狙っています。
では、TRACEの第1段階であるエンコーダの事前学習から、具体的な設計を見ていきましょう。
Stage 1: 時系列エンコーダの事前学習
TRACEの学習は2段階で行われます。Stage 1では、時系列データの良質な表現を獲得するために、Masked Autoencoder (MAE) による自己教師あり事前学習を行います。ラベルもテキストも不要で、時系列データだけから構造的な特徴を学びます。
詳細に入る前に、論文のアーキテクチャ全体図で各部品の位置関係を掴んでおきましょう。

出典: Chen et al., “TRACE: Grounding Time Series in Context for Multimodal Embedding and Retrieval”, NeurIPS 2025, Figure 3.
図の左半分が時系列側です。[CLS] と各チャネルの [CIT] を含むトークン列が Channel-biased Attention + RoPE を持つ Encoder-only Transformer に入り、上部の再構成・予測・分類ヘッドにつながります(Stage 1)。右半分がテキスト側で、フリーズした Sentence Encoder が出力する記述ベクトル $z_1, z_2, \dots$ と、時系列の CIT 表現が Cross Attention で結びつき、対照学習(attract/repel の矢印)で揃えられます(Stage 2)。$z’_{\text{cxt}}$ はバッチ内のハードネガティブです。これから、この図の各ブロックを左から順に解説していきます。
パッチ分割と埋め込み
まず、入力となる多変量時系列データの形式を定義します。$C$ 個のチャネルと $T$ 個の時間ステップを持つ多変量時系列は、行列として次のように表されます。
$$ \bm{X} \in \mathbb{R}^{C \times T} $$
例えば、気温・湿度・気圧の3チャネルを1時間ごとに24ステップ記録したデータなら、$C = 3, T = 24$ の行列です。
Vision Transformer (ViT) が画像をパッチに分割するのと同様に、TRACEは各チャネルの時系列を長さ $P$ の重なりのないパッチに分割します。各チャネルあたりのパッチ数は次の通りです。
$$ \hat{T} = \left\lfloor \frac{T}{P} \right\rfloor $$
各パッチは長さ $P$ の1次元ベクトルです。これを線形射影によって $d$ 次元の埋め込みベクトルに変換します。
$$ \bm{e}_{c,t} = \bm{W}_{\text{proj}} \bm{x}_{c,t} + \bm{b}_{\text{proj}}, \quad \bm{W}_{\text{proj}} \in \mathbb{R}^{d \times P} $$
ここで $\bm{x}_{c,t} \in \mathbb{R}^P$ はチャネル $c$ の $t$ 番目のパッチ、$\bm{e}_{c,t} \in \mathbb{R}^d$ はその埋め込みベクトルです。
このパッチ化により、$T$ 個の時間ステップが $\hat{T}$ 個のトークンに圧縮され、Transformerが効率的に処理できるシーケンス長になります。しかし、ここで1つ重要な問題が生じます — 複数のチャネルのパッチを一列に並べてTransformerに入力すると、どのトークンがどのチャネルに属するかの情報が失われてしまいます。この問題を解決するのが、TRACEの核心的な貢献である Channel Identity Token です。
Channel Identity Token (CIT) の導入と役割
Channel Identity Token (CIT) は、TRACEの最も重要な技術的貢献の一つです。その発想は極めて直感的です。
教室の中に、数学、物理、化学の3つのグループがあるとしましょう。各グループのメンバーは自分がどのグループに属しているかを知っていますが、外部の観察者にはそれがわかりません。そこで、各グループにリーダーバッジを付けたグループ長を置きます。このグループ長が、グループ全体を代表して外部とコミュニケーションを取ります。CITは、まさにこの「グループ長のバッジ付きトークン」に相当します。
具体的には、CITはチャネルごとに1つ用意される 学習可能なベクトル $\bm{c}_c \in \mathbb{R}^{1 \times d}$ です。各チャネル $c$ のパッチ列の先頭にこのCITが挿入されます。さらに、全チャネルに共通する [CLS] トークン(サンプル全体の表現を集約する特殊トークン)がシーケンスの先頭に置かれます。
これにより、Transformerに入力されるトークン列は次のように構成されます。
$$ [\text{CLS}]; \; [\text{CIT}]_1, \bm{e}_{1,1}, \bm{e}_{1,2}, \dots, \bm{e}_{1,\hat{T}}; \; [\text{CIT}]_2, \bm{e}_{2,1}, \dots, \bm{e}_{2,\hat{T}}; \; \dots; \; [\text{CIT}]_C, \bm{e}_{C,1}, \dots, \bm{e}_{C,\hat{T}} $$
全体のシーケンス長は次の通りです。
$$ L = C(\hat{T} + 1) + 1 $$
$C(\hat{T} + 1)$ は各チャネルの(CIT 1個 + パッチ $\hat{T}$ 個)の合計で、$+1$ はグローバルな [CLS] トークンの分です。

トークン列を図にすると上のようになります。先頭に全体を集約する [CLS]、続いて各チャネルごとに「代表トークン [CIT]$_c$ + そのチャネルのパッチ列」が並びます。パッチ $P_{c,t}$ は長さ $P$ の生の時系列を線形射影で $d$ 次元に変換したものです。色分けされたCITが各チャネルの「見出し」として機能し、後段でチャネルごとのテキストと対応づける足がかりになります。
CITが果たす役割は3つあります。
- チャネル識別: CITのインデックスにより、Transformerはどのトークンがどのチャネルに属するかを知ることができます
- チャネル集約: Self-Attentionの学習を通じて、各CITはそのチャネル全体の意味的な要約を獲得します。これは、[CLS] トークンがシーケンス全体の要約を獲得するのと同じメカニズムです
- チャネルレベルのアライメント: Stage 2のマルチモーダル学習で、各CITがチャネルごとのテキスト記述と対応付けられます
しかし、CITをただ挿入するだけでは十分ではありません。標準的なSelf-Attentionでは、すべてのトークンが他のすべてのトークンにアテンションを向けるため、異なるチャネルの情報が混ざり合ってしまいます。「気温チャネルのCIT」が「湿度チャネルのパッチ」にアテンションしてしまうと、チャネルごとの独立した表現が崩れてしまうのです。この問題を解決するのが、Channel-Biased Attention です。
Channel-Biased Attention (CbA) — チャネルの意味的分離を保証する
Channel-Biased Attention (CbA) は、「どのトークンがどのトークンにアテンションできるか」にバイアスをかけることで、チャネルの意味的な分離を保ちつつ、必要な情報交換を許すメカニズムです。
日常的なアナロジーで説明しましょう。会議室に3つのチーム(気温チーム、湿度チーム、気圧チーム)がいて、各チームにはリーダー(CIT)がいるとします。CbAのルールは次の通りです。
- 各チームのリーダーは、自分のチームメンバーの意見だけを集約する(CITは同一チャネルのパッチにのみアテンション)
- チームメンバーは、他チームのメンバーとも自由に情報交換できる(パッチトークンは全トークンにアテンション可能)
- 全体の議長([CLS])は、全員の意見を聞ける([CLS]は全トークンにアテンション可能)
この制約を数学的に表現するために、バイナリマスク $\bm{M} \in \{0, 1\}^{L \times L}$ を定義します。$M_{ij} = 1$ ならトークン $i$ はトークン $j$ にアテンションでき、$M_{ij} = 0$ ならアテンションがブロックされます。
CITトークンに対するマスクの規則は次の通りです。
$$ M_{ij} = \begin{cases} 1 & \text{if } i \text{ is } [\text{CIT}]_c \text{ and } j \text{ belongs to channel } c \\ 0 & \text{if } i \text{ is } [\text{CIT}]_c \text{ and } j \text{ belongs to channel } c’ \neq c \end{cases} $$
通常のパッチトークンと [CLS] トークンに対しては、すべてのトークンへのアテンションが許可されます($M_{ij} = 1$ for all $j$)。

このマスク行列 $\bm{M}$ を可視化すると、CbAの仕組みが一目でわかります(青=アテンション可、白=ブロック)。橙色で囲った CIT行だけが横に細く、自分のチャネルのパッチと [CLS] にしかアテンションできません。例えば $\text{CIT}_2$ の行は、$\text{CIT}_2$ と $P2$ 系のパッチ、そして [CLS] の列だけが青く、チャネル1・3のトークンは白くブロックされています。一方、[CLS] 行とパッチ行は全列が青で、自由に情報を集められます。この「CIT だけ視野を絞る」設計が、チャネル表現の純度を保つ鍵です。
さらに、TRACEはRotary Position Embedding (RoPE) を位置エンコーディングとして採用しています。ここで重要なのは、RoPEがチャネルごとに独立に適用される点です。つまり、各チャネル内でのパッチの相対位置関係のみがエンコードされ、チャネルをまたいだ位置情報は注入されません。

RoPEの扱いで重要なのは、位置を「平坦化したトークンの並び順」ではなく「元の時間位置」で測る点です(左図)。複数チャネルを一列に連結すると、本来同じ時刻のパッチが系列上は遠くに離れてしまいます。そこで相対位置を実時間差 $\Delta t$ で定義し、チャネルをまたいだ位置情報が混ざらないようにします。RoPE自体は、位置に応じてQuery・Keyベクトルを回転させ(右図)、内積に相対位置の情報を埋め込む手法です。回転角の差がそのまま相対位置を表すため、絶対位置に依存しない安定した位置エンコーディングになります。
これらを組み合わせた CbA のアテンションスコアは、次の式で計算されます。
$$ \alpha_{ij} = \text{softmax}_j \left( \frac{\bm{Q}_i^T \bm{R}_{\theta, \Delta t_{ij}} \bm{K}_j}{\sqrt{d}} + \log M_{ij} \right) $$
各変数の意味を整理しましょう。
- $\bm{Q}_i, \bm{K}_j \in \mathbb{R}^d$: トークン $i$ のQueryベクトルとトークン $j$ のKeyベクトル
- $\bm{R}_{\theta, \Delta t_{ij}}$: RoPEの回転行列で、$\Delta t_{ij}$ はトークン $i$ と $j$ の相対位置
- $\sqrt{d}$: スケーリング係数(Scaled Dot-Product Attentionと同じ)
- $\log M_{ij}$: マスク項。$M_{ij} = 1$ なら $\log 1 = 0$(アテンションに影響なし)、$M_{ij} = 0$ なら $\log 0 = -\infty$(softmax後にアテンション重みが0になる)
$\log M_{ij}$ の項がマスクとして機能する仕組みは巧みです。softmaxの引数に $-\infty$ を加えると、その位置のアテンション重みは $e^{-\infty} = 0$ となるため、アテンションが完全にブロックされます。これは、Transformerのパディングマスクやデコーダのcausal maskと同じ原理です。
CbAの設計思想をまとめると、次のようになります。CITは「チャネルの代表トークン」としてチャネル内の情報だけを集約するため、チャネルレベルの純粋な表現が得られます。一方、パッチトークンは他のチャネルとも自由に情報交換できるため、チャネル間の相互作用(例えば、気温低下と気圧上昇の同時発生)も学習できます。この「選択的な情報フロー」により、CITがチャネルごとの独立した意味を保持しながら、全体としてはチャネル間の関係も捉えられるのです。
MAEによるマスク再構成事前学習
Stage 1の学習目標は、上記のアーキテクチャ(パッチ化 + CIT + CbA)を備えたTransformerエンコーダに対して、Masked Autoencoder (MAE) によるマスク再構成タスクを適用することです。
手順は次の通りです。
- 入力時系列をパッチ化し、CITを付与したトークン列を構成します
- パッチトークンの一定割合をランダムにマスクします(CITと[CLS]はマスクしません)
- マスクされていないトークンのみをエンコーダに入力し、潜在表現を獲得します
- デコーダがマスクされたパッチの元の値を再構成します
- 再構成された値と元の値の平均二乗誤差(MSE)を損失関数として最小化します
$$ \mathcal{L}_{\text{MAE}} = \frac{1}{|\mathcal{M}|} \sum_{(c,t) \in \mathcal{M}} \left\| \hat{\bm{x}}_{c,t} – \bm{x}_{c,t} \right\|^2 $$
ここで $\mathcal{M}$ はマスクされたパッチの集合、$\hat{\bm{x}}_{c,t}$ は再構成されたパッチ、$\bm{x}_{c,t}$ は元のパッチです。

この図がStage 1のMAE事前学習のイメージです。時系列をパッチに区切り、一定割合(斜線のパッチ)をランダムに隠します。エンコーダには観測パッチだけを入力し、デコーダが隠された部分の値を周囲の文脈から復元します。「?」の部分を埋めるには、時間的な連続性やチャネル内のパターンを理解する必要があるため、モデルは自然と良質な表現を獲得します。ラベルもテキストも使わずに学習できるのが自己教師あり学習の強みです。
さらに、TRACEでは入力にインスタンス正規化(instance normalization)を適用しています。これは、各サンプルの各チャネルに対して平均を引いて標準偏差で割る操作で、分布の違い(distribution shift)に対するロバスト性を高めます。
MAE事前学習を通じて、エンコーダは「マスクされた部分を予測するために、時系列の時間的な構造やチャネル内のパターンを理解しなければならない」状況に置かれます。この結果、各CITはそのチャネルの時間的パターンを要約する高品質な表現を獲得し、[CLS]トークンはサンプル全体を俯瞰する表現を獲得します。
Stage 1で時系列の良質な表現が得られたところで、次はこの表現をテキストと結びつける Stage 2 のマルチモーダルアライメントに進みましょう。
Stage 2: マルチモーダルアライメント
Stage 2の目標は、Stage 1で事前学習された時系列エンコーダの出力をテキストと同じ埋め込み空間に配置することです。これにより、「時系列パターン」と「そのパターンを記述するテキスト」が埋め込み空間上で近い位置にマッピングされ、テキストによる時系列の検索やその逆が可能になります。
テキストエンコーダ
TRACEのテキスト側は、事前学習済みの Sentence-Transformers(例: all-MiniLM-L6-v2)をフリーズ(重みを固定)した状態で使用します。テキストエンコーダは学習中に更新されないため、既に獲得されている自然言語の意味空間がそのまま活用されます。
テキストは2つのレベルで用意されます。
- サンプルレベルの記述: サンプル全体を要約するテキスト(例: 「2023年1月の東京の気象データ。寒冷前線の通過により急激な気温変化が見られる」)
- チャネルレベルの記述: 各チャネルに固有のテキスト(例: 気温チャネルに対して「前線通過時に10度以上の急激な低下」、湿度チャネルに対して「前線通過後に湿度が80%以上に上昇」)
テキストエンコーダの出力を $d’$ 次元のベクトルとすると、線形射影によって時系列エンコーダと同じ $d$ 次元に揃えます。
サンプルレベルのテキスト埋め込みを $\bm{t}_{\text{global}} \in \mathbb{R}^d$、チャネル $c$ のテキスト埋め込みを $\bm{t}_c \in \mathbb{R}^d$ とします。
[CLS]と[CIT]による2層構造の表現
Stage 1で事前学習されたエンコーダから、2種類の表現を抽出します。
- [CLS]トークンの出力 $\bm{z}_{\text{global}}$: サンプル全体を要約するグローバル表現
- [CIT]$_c$ トークンの出力 $\bm{z}_c$: チャネル $c$ を要約するチャネルレベル表現
この2層構造は、TRACEの設計の核心です。サンプルレベルの対照学習([CLS] $\leftrightarrow$ サンプルテキスト)では、時系列全体の大まかな意味を捉えます。チャネルレベルの対照学習([CIT]$_c$ $\leftrightarrow$ チャネルテキスト)では、各チャネルの細かい意味を捉えます。
人間の理解でたとえると、「この気象データは寒冷前線の通過を示している」(サンプルレベル)という粗い理解と、「気温チャネルは急降下を、気圧チャネルは急上昇を示している」(チャネルレベル)という細かい理解の両方を同時に学習するイメージです。
Cross-Attentionによるチャネル表現の洗練
Stage 2では、時系列のチャネル表現 $\bm{z}_c$ をさらに洗練するために、Cross-Attention機構が導入されています。
Cross-Attentionでは、テキスト埋め込みをQueryとし、時系列のCIT埋め込みをKey・Valueとします。これにより、テキストが「自分にとって重要なチャネル情報はどれか」を選択的に取り出すことができます。
具体的には、チャネル $c$ のCIT出力 $\bm{z}_c$ に対して、テキスト埋め込み $\bm{t}_c$ を用いたCross-Attentionを適用し、洗練されたチャネル表現 $\tilde{\bm{z}}_c$ を得ます。この洗練過程により、テキストの意味空間に適合した時系列表現が構成されます。
同様に、[CLS]トークンの出力に対してもCross-Attentionを適用し、サンプルレベルの洗練された表現 $\tilde{\bm{z}}_{\text{global}}$ を得ます。
この2段階の構造 — 事前学習で時系列の構造を学び、次にCross-Attentionでテキストとの接続を構築する — は、TRACEの安定した学習に不可欠です。いきなりマルチモーダルの対照学習を行うと、時系列エンコーダとテキストエンコーダの表現空間がまったく異なるため、学習が収束しにくくなります。
さて、対照学習を行うためには「正例」と「負例」を定義する必要があります。特に、より難しい負例(ハードネガティブ)を用いることで、モデルの判別能力を高めることができます。次はTRACEのHard Negative Mining戦略を見ていきましょう。
Hard Negative Mining — より難しい負例で学習を加速する
対照学習の品質は、負例の質に大きく依存します。ランダムに選ばれた負例は、アンカーとまったく似ていないことが多く、モデルにとって「簡単すぎる」問題になりがちです。例えば、「真夏の猛暑日の気象データ」と「真冬の降雪日の気象データ」を区別するのは簡単ですが、「寒冷前線通過直前のデータ」と「寒冷前線通過直後のデータ」を区別するのは難しいです。この「難しいが異なる」負例こそが、モデルの表現を繊細に磨き上げるのに必要なのです。
TRACEは、サンプルレベルとチャネルレベルの両方でハードネガティブマイニングを行います。
サンプルレベルのハードネガティブ
サンプルレベルでは、現在のモデルが「最も混同しやすい」異なるサンプルをハードネガティブとして選択します。具体的には、ミニバッチ内の全サンプルに対して、[CLS]トークンの埋め込み間のコサイン類似度を計算します。
$$ \text{sim}(\bm{z}_{\text{global}}^{(i)}, \bm{z}_{\text{global}}^{(j)}) = \frac{\bm{z}_{\text{global}}^{(i) \top} \bm{z}_{\text{global}}^{(j)}}{\|\bm{z}_{\text{global}}^{(i)}\| \cdot \|\bm{z}_{\text{global}}^{(j)}\|} $$
サンプル $i$ に対するハードネガティブは、$j \neq i$ かつ類似度が最も高いサンプルです。つまり、「異なるサンプルだが、現在のモデルから見ると最も似ている」ものを選びます。
チャネルレベルのハードネガティブ
チャネルレベルでは、より巧みなハードネガティブ戦略が採用されています。チャネル $c$ のCIT埋め込み $\bm{z}_c^{(i)}$ に対して、2種類のハードネガティブが用意されます。
Intra-instance distractors(同一サンプル内の他チャネル): 同じサンプル $i$ に属する、チャネル $c$ とは異なるチャネル $c’$ のCIT埋め込み $\bm{z}_{c’}^{(i)}$ です。例えば、「サンプル $i$ の気温チャネル」に対するハードネガティブとして、「サンプル $i$ の湿度チャネル」が使われます。これは同じ時間帯のデータなので、時間的なパターンが類似している可能性が高く、チャネル固有の特徴を正確に区別することをモデルに強制します。
Inter-instance distractors(異なるサンプルの同一チャネル): 異なるサンプル $j \neq i$ の同じチャネル $c$ のCIT埋め込み $\bm{z}_c^{(j)}$ のうち、類似度が高いものです。例えば、「サンプル $i$ の気温チャネル」に対して、「別の日のサンプル $j$ の気温チャネルで、パターンが似ているもの」が使われます。
この2種類のハードネガティブの組み合わせにより、モデルは以下の2つの判別能力を同時に鍛えられます。
- 同じサンプル内の異なるチャネルを区別する能力(intra-instance)
- 異なるサンプルの同じチャネルを区別する能力(inter-instance)

図は、サンプル $i$ の気温チャネル(橙枠)をアンカーとしたときの2種類のハードネガティブを示しています。intra(赤・同じサンプル内の湿度・気圧チャネル)は、同じ時間帯のデータなので時間的パターンが似ており、「チャネルの違い」を正確に学ばせます。inter(紫・別サンプル $j$ の気温チャネルで、パターンが似たもの)は、「同じ物理量でも別の事象」を区別させます。簡単な負例(真夏と真冬のように明らかに違うもの)ではなく、この「似ているが異なる」境界例を選ぶことが、表現を繊細に磨き上げるポイントです。
このハードネガティブ戦略が、TRACEのチャネルレベルの表現品質を大幅に向上させていることが、後述のアブレーション実験で確認されています。
では、これらの正例・負例ペアを用いて、具体的な損失関数がどのように定義されているかを見ていきましょう。
損失関数の詳細 — 双方向InfoNCE
TRACEの損失関数は、CLIPで使われている双方向InfoNCE損失を、サンプルレベルとチャネルレベルの2つの粒度に拡張したものです。
InfoNCE損失の基本形
まず、InfoNCE損失の基本形を振り返りましょう。アンカー $\bm{a}$ と正例 $\bm{p}$ のペアに対して、$K$ 個の負例 $\{\bm{n}_1, \bm{n}_2, \dots, \bm{n}_K\}$ がある場合、InfoNCE損失は次のように定義されます。
$$ \mathcal{L}_{\text{InfoNCE}} = -\log \frac{\exp(\text{sim}(\bm{a}, \bm{p}) / \tau)}{\exp(\text{sim}(\bm{a}, \bm{p}) / \tau) + \sum_{k=1}^{K} \exp(\text{sim}(\bm{a}, \bm{n}_k) / \tau)} $$
ここで $\tau$ は温度パラメータです。温度が低いほど、類似度の差が損失に与える影響が大きくなり、モデルはより厳密な区別を強いられます。

InfoNCEがやっていることは、この類似度行列の対角成分(正しいテキスト–時系列ペア)を最も高くすることだと捉えると直感的です。上の図は、後で実装する簡易版TRACEを学習させた後の、テキストクエリ(行)と時系列サンプル(列)のコサイン類似度行列です。各行で黒枠の対角セルが最も明るくなる傾向が見て取れます。テキスト→時系列方向は各行のsoftmax、時系列→テキスト方向は各列のsoftmaxに対応し、両方向を同時に最適化するのが双方向InfoNCEです。
サンプルレベルInfoNCE
サンプルレベルでは、[CLS]トークンの時系列表現 $\tilde{\bm{z}}_{\text{global}}^{(i)}$ とサンプルテキスト表現 $\bm{t}_{\text{global}}^{(i)}$ が正例ペアを形成します。
テキスト→時系列方向(テキストをクエリとして、正しい時系列を見つける)の損失は次の通りです。
$$ \mathcal{L}_{\text{text} \to \text{ts}}^{\text{global}} = -\frac{1}{N} \sum_{i=1}^{N} \log \frac{\exp(\text{sim}(\bm{t}_{\text{global}}^{(i)}, \tilde{\bm{z}}_{\text{global}}^{(i)}) / \tau)}{\sum_{j=1}^{N} \exp(\text{sim}(\bm{t}_{\text{global}}^{(i)}, \tilde{\bm{z}}_{\text{global}}^{(j)}) / \tau)} $$
ここで $N$ はミニバッチ内のサンプル数です。分子は正しいテキスト-時系列ペアの類似度、分母はそのテキストと全時系列サンプルの類似度の和です。
同様に、時系列→テキスト方向の損失 $\mathcal{L}_{\text{ts} \to \text{text}}^{\text{global}}$ も対称的に定義されます。時系列をクエリとして、正しいテキストを見つける方向です。
チャネルレベルInfoNCE
チャネルレベルでは、チャネル $c$ のCIT表現 $\tilde{\bm{z}}_c^{(i)}$ とチャネルテキスト表現 $\bm{t}_c^{(i)}$ が正例ペアを形成します。
ここでの負例集合が重要です。前節で述べた通り、チャネルレベルの負例には intra-instance distractors(同じサンプルの他チャネル)と inter-instance distractors(他サンプルの同チャネル)が含まれます。
テキスト→時系列方向のチャネルレベル損失は、各チャネルのテキストに対して、正しいチャネルの時系列表現を見つける問題として定式化されます。
$$ \mathcal{L}_{\text{text} \to \text{ts}}^{\text{channel}} = -\frac{1}{N \cdot C} \sum_{i=1}^{N} \sum_{c=1}^{C} \log \frac{\exp(\text{sim}(\bm{t}_c^{(i)}, \tilde{\bm{z}}_c^{(i)}) / \tau)}{\sum_{(j, c’) \in \mathcal{N}(i,c)} \exp(\text{sim}(\bm{t}_c^{(i)}, \tilde{\bm{z}}_{c’}^{(j)}) / \tau)} $$
ここで $\mathcal{N}(i, c)$ はサンプル $i$ のチャネル $c$ に対する負例の集合で、intra-instance distractors と inter-instance distractors の両方を含みます。
統合損失
最終的な損失関数は、サンプルレベルとチャネルレベルの損失を重み付きで加算したものです。
$$ \mathcal{L}_{\text{align}} = \frac{1}{2}\left(\mathcal{L}_{\text{text} \to \text{ts}}^{\text{global}} + \mathcal{L}_{\text{ts} \to \text{text}}^{\text{global}}\right) + \lambda_{\text{ch}} \cdot \frac{1}{2}\left(\mathcal{L}_{\text{text} \to \text{ts}}^{\text{channel}} + \mathcal{L}_{\text{ts} \to \text{text}}^{\text{channel}}\right) $$
$\lambda_{\text{ch}}$ はチャネルレベルの損失の重みを制御するハイパーパラメータです。各項の $\frac{1}{2}$ は双方向(テキスト→時系列と時系列→テキスト)の平均を取るためのものです。
この式の構造を分解して理解しましょう。
- 第1項 $\frac{1}{2}(\mathcal{L}_{\text{text} \to \text{ts}}^{\text{global}} + \mathcal{L}_{\text{ts} \to \text{text}}^{\text{global}})$ は、サンプル全体の意味的な対応を学習します。「この時系列全体は、このテキスト全体に対応する」という粗い粒度のアライメントです
- 第2項 $\lambda_{\text{ch}} \cdot \frac{1}{2}(\mathcal{L}_{\text{text} \to \text{ts}}^{\text{channel}} + \mathcal{L}_{\text{ts} \to \text{text}}^{\text{channel}})$ は、各チャネルの意味的な対応を学習します。「このチャネルの時系列パターンは、このチャネル記述テキストに対応する」という細かい粒度のアライメントです
双方向にすることで、テキストから時系列を検索するタスクと、時系列からテキストを検索するタスクの両方に対して最適化されます。
損失関数の全体像が見えたところで、次はTRACEが学習後にどのような下流タスクに適用できるかを見ていきましょう。
下流タスクへの適応 — 検索、RAG、スタンドアロンエンコーダ
TRACEの学習が完了すると、時系列とテキストが共通の埋め込み空間に配置されています。この学習済みモデルは、様々な下流タスクに活用できます。
検索タスク(Text-to-Timeseries / Timeseries-to-Text)
最も直接的な応用は、クロスモーダル検索です。
Text-to-Timeseries (T2TS): テキストクエリ(例:「急激な気温低下を伴う寒冷前線通過」)を入力し、データベース内の時系列サンプルを類似度順にランキングします。テキストエンコーダでクエリを埋め込み、事前に計算された時系列埋め込みとのコサイン類似度を計算するだけです。
Timeseries-to-Text (TS2T): 時系列サンプルを入力し、その意味を最もよく記述するテキストを検索します。これは「この時系列パターンは何を意味するか」を自動的にテキストで説明する機能です。
検索は、サンプルレベル([CLS]トークン同士の類似度)でもチャネルレベル([CIT]トークン同士の類似度)でも行えます。例えば、「気温チャネルだけが類似したサンプル」を検索するという、チャネル指定の検索も可能です。
RAGによる時系列予測の改善
TRACEの検索能力は、RAG(Retrieval-Augmented Generation)と組み合わせることで、時系列基盤モデルの予測精度を向上させることができます。
具体的には、予測したい時系列に対して、TRACEを用いてデータベースから類似パターンを検索し、その検索結果を時系列予測モデルのコンテキスト(追加入力)として提供します。Chen et al. (2025) は、この RAG アプローチにより、時系列予測の精度が向上することを実験的に示しています。
スタンドアロンエンコーダとしての利用
TRACEの時系列エンコーダは、マルチモーダルアライメントの枠組みから切り離して、スタンドアロンのエンコーダとしても利用できます。[CLS]トークンの出力をサンプル全体の表現として利用し、その上に軽量な分類ヘッドや回帰ヘッドを載せることで、予測(forecasting)や分類(classification)のタスクに適用できます。
重要なのは、この軽量なタスク固有のチューニング(fine-tuning)を行っても、マルチモーダルアライメントの能力が維持される点です。つまり、予測タスク用にファインチューニングしたモデルでも、テキストによる検索が引き続き可能です。これは、TRACEの事前学習とアライメント学習が十分に汎用的な表現を獲得していることを示しています。
ここまでで、TRACEの理論的な全体像が把握できました。次は、コア部分をPyTorchで実装して、理論の理解を深めましょう。
Python実装: 簡易版TRACE
ここでは、TRACEの核心的な要素 — パッチ化、Channel Identity Token (CIT)、Channel-Biased Attention (CbA)、InfoNCE損失 — を PyTorch で実装します。フルスケールの実装ではなく、理論的な理解を深めるための簡易版として構成しています。
パッチ化とCITの付与
まず、多変量時系列をパッチに分割し、CITと[CLS]トークンを付与するモジュールを実装します。
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib, matplotlib.pyplot as plt
# 日本語フォント設定(グラフのラベル用)
for cand in ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", "IPAexGothic", "Meiryo"]:
if any(cand == f.name for f in matplotlib.font_manager.fontManager.ttflist):
plt.rcParams["font.family"] = cand
break
plt.rcParams["axes.unicode_minus"] = False
class TimeSeriesPatchEmbedding(nn.Module):
"""多変量時系列をパッチ化し、CITと[CLS]トークンを付与する"""
def __init__(self, num_channels, patch_len, d_model):
super().__init__()
self.num_channels = num_channels
self.patch_len = patch_len
self.d_model = d_model
# パッチを d_model 次元に射影する線形層
self.patch_proj = nn.Linear(patch_len, d_model)
# Channel Identity Tokens (各チャネルに1つ)
self.cit_tokens = nn.Parameter(torch.randn(num_channels, 1, d_model) * 0.02)
# [CLS] トークン (サンプル全体の表現)
self.cls_token = nn.Parameter(torch.randn(1, 1, d_model) * 0.02)
def forward(self, x):
"""
x: (batch, channels, time_steps)
returns: (batch, seq_len, d_model), channel_boundaries
"""
B, C, T = x.shape
P = self.patch_len
num_patches = T // P
# インスタンス正規化 (各サンプル・各チャネル)
mean = x.mean(dim=-1, keepdim=True)
std = x.std(dim=-1, keepdim=True) + 1e-8
x = (x - mean) / std
# パッチ分割: (B, C, T) -> (B, C, num_patches, P)
x = x[:, :, :num_patches * P].reshape(B, C, num_patches, P)
# 線形射影: (B, C, num_patches, P) -> (B, C, num_patches, d_model)
patches = self.patch_proj(x)
# 各チャネルの先頭にCITを付与
cit_expanded = self.cit_tokens.unsqueeze(0).expand(B, -1, -1, -1)
# (B, C, num_patches+1, d_model)
channel_seqs = torch.cat([cit_expanded, patches], dim=2)
# 全チャネルを一列に連結: (B, C*(num_patches+1), d_model)
flat_seq = channel_seqs.reshape(B, C * (num_patches + 1), self.d_model)
# [CLS]トークンを先頭に付与
cls_expanded = self.cls_token.expand(B, -1, -1)
full_seq = torch.cat([cls_expanded, flat_seq], dim=1)
# チャネル境界情報 (マスク生成に使用)
patches_per_channel = num_patches + 1 # CIT + patches
return full_seq, patches_per_channel
このモジュールは、入力の多変量時系列 $(B, C, T)$ を受け取り、$[\text{CLS}]; [\text{CIT}]_1; \text{patches}_1; \dots; [\text{CIT}]_C; \text{patches}_C$ のトークン列に変換します。インスタンス正規化が先頭で適用されている点に注目してください。これにより、異なるスケールの時系列データに対してもロバストな表現が得られます。
Channel-Biased Attentionの実装
次に、CITのアテンションを同一チャネル内に制限するCbAを実装します。
class ChannelBiasedAttention(nn.Module):
"""Channel-Biased Attention: CITのアテンションをチャネル内に制限"""
def __init__(self, d_model, num_heads):
super().__init__()
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
assert d_model % num_heads == 0
self.qkv = nn.Linear(d_model, 3 * d_model)
self.out_proj = nn.Linear(d_model, d_model)
def build_cba_mask(self, seq_len, num_channels, patches_per_channel):
"""
CbAマスクを構築する
- [CLS] (index 0): 全トークンにアテンション可
- [CIT]_c: 同じチャネルcのトークンにのみアテンション可
- パッチ: 全トークンにアテンション可
"""
mask = torch.ones(seq_len, seq_len, dtype=torch.bool)
for c in range(num_channels):
# チャネルcの開始インデックス (CLS=0の後)
ch_start = 1 + c * patches_per_channel
cit_idx = ch_start # CITの位置
ch_end = ch_start + patches_per_channel
# CITは自チャネルの範囲内のみアテンション可
cit_mask = torch.zeros(seq_len, dtype=torch.bool)
cit_mask[ch_start:ch_end] = True
cit_mask[0] = True # [CLS]へのアテンションも許可
mask[cit_idx] = cit_mask
return mask
def forward(self, x, num_channels, patches_per_channel):
"""
x: (B, L, d_model)
returns: (B, L, d_model)
"""
B, L, D = x.shape
# QKV射影
qkv = self.qkv(x).reshape(B, L, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4)
q, k, v = qkv[0], qkv[1], qkv[2]
# スケーリング
scale = self.head_dim ** -0.5
attn = torch.matmul(q, k.transpose(-2, -1)) * scale
# CbAマスクの適用
cba_mask = self.build_cba_mask(L, num_channels, patches_per_channel)
cba_mask = cba_mask.to(x.device)
# マスク: Falseの位置を-infに (log(0) = -inf)
attn = attn.masked_fill(~cba_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
attn = F.softmax(attn, dim=-1)
attn = torch.nan_to_num(attn, nan=0.0)
out = torch.matmul(attn, v)
out = out.transpose(1, 2).reshape(B, L, D)
out = self.out_proj(out)
return out
build_cba_mask メソッドが CbA の核心です。マスク行列を構築する際、CITの行だけが制限され、[CLS]トークンとパッチトークンの行は全位置にアテンション可能です。masked_fill で False の位置を $-\infty$ に設定すると、softmax の後にアテンション重みが $0$ になります。これは論文中の $\log M_{ij}$ の項と等価な操作です。
Transformerエンコーダブロック
CbAを組み込んだTransformerエンコーダブロックを定義します。
class TRACEEncoderBlock(nn.Module):
"""CbAを使ったTransformerエンコーダブロック"""
def __init__(self, d_model, num_heads, ff_dim, dropout=0.1):
super().__init__()
self.attn = ChannelBiasedAttention(d_model, num_heads)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.ff = nn.Sequential(
nn.Linear(d_model, ff_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(ff_dim, d_model),
nn.Dropout(dropout),
)
def forward(self, x, num_channels, patches_per_channel):
# Pre-norm + Residual + CbA
x = x + self.attn(self.norm1(x), num_channels, patches_per_channel)
x = x + self.ff(self.norm2(x))
return x
標準的なTransformerブロックとの唯一の違いは、Self-Attentionの部分がCbAに置き換えられている点です。Feed-Forward Network、残差接続、Layer Normalizationはそのままです。
InfoNCE損失の実装
サンプルレベルとチャネルレベルの双方向InfoNCE損失を実装します。
class BidirectionalInfoNCE(nn.Module):
"""双方向InfoNCE損失(サンプルレベル + チャネルレベル)"""
def __init__(self, temperature=0.07, lambda_ch=0.5):
super().__init__()
self.temperature = temperature
self.lambda_ch = lambda_ch
def infonce_loss(self, anchors, positives, temperature):
"""
anchors: (N, d) - アンカー埋め込み
positives: (N, d) - 正例埋め込み
N個の対角要素が正例ペア
"""
# 正規化
anchors = F.normalize(anchors, dim=-1)
positives = F.normalize(positives, dim=-1)
# 類似度行列: (N, N)
logits = torch.matmul(anchors, positives.T) / temperature
# 対角要素が正例
labels = torch.arange(logits.size(0), device=logits.device)
# 双方向
loss_a2p = F.cross_entropy(logits, labels)
loss_p2a = F.cross_entropy(logits.T, labels)
return (loss_a2p + loss_p2a) / 2
def forward(self, ts_global, text_global, ts_channels, text_channels):
"""
ts_global: (B, d) - [CLS]の時系列埋め込み
text_global: (B, d) - サンプルテキスト埋め込み
ts_channels: (B, C, d) - [CIT]の時系列埋め込み
text_channels: (B, C, d) - チャネルテキスト埋め込み
"""
# サンプルレベルInfoNCE
loss_global = self.infonce_loss(ts_global, text_global, self.temperature)
# チャネルレベルInfoNCE
B, C, d = ts_channels.shape
# 全チャネルをフラットにして1つの対照学習問題に
ts_flat = ts_channels.reshape(B * C, d)
text_flat = text_channels.reshape(B * C, d)
loss_channel = self.infonce_loss(ts_flat, text_flat, self.temperature)
# 統合損失
total_loss = loss_global + self.lambda_ch * loss_channel
return total_loss, loss_global, loss_channel
infonce_loss メソッドは、CLIPスタイルの双方向InfoNCE損失を計算します。類似度行列の対角要素が正例ペアに対応し、非対角要素が負例です。F.cross_entropy は、softmax + 負の対数尤度をまとめて計算する PyTorch の関数です。
統合モデルと学習ループ
すべてのコンポーネントを統合したTRACEモデルと学習ループを実装します。
class SimpleTRACE(nn.Module):
"""簡易版TRACEモデル"""
def __init__(self, num_channels, patch_len, d_model, num_heads,
ff_dim, num_layers, text_dim=384):
super().__init__()
self.num_channels = num_channels
self.patch_embed = TimeSeriesPatchEmbedding(num_channels, patch_len, d_model)
self.encoder_blocks = nn.ModuleList([
TRACEEncoderBlock(d_model, num_heads, ff_dim)
for _ in range(num_layers)
])
# テキスト埋め込みの射影 (text_dim -> d_model)
self.text_proj = nn.Linear(text_dim, d_model)
# Cross-Attention (簡易版: 線形射影で代替)
self.cross_attn_global = nn.Linear(d_model, d_model)
self.cross_attn_channel = nn.Linear(d_model, d_model)
def encode_timeseries(self, x):
"""時系列をエンコードし、[CLS]と[CIT]の表現を返す"""
seq, patches_per_channel = self.patch_embed(x)
for block in self.encoder_blocks:
seq = block(seq, self.num_channels, patches_per_channel)
# [CLS]トークン (index 0)
cls_out = seq[:, 0, :]
# [CIT]トークン (各チャネルの先頭)
cit_indices = [1 + c * patches_per_channel for c in range(self.num_channels)]
cit_out = seq[:, cit_indices, :] # (B, C, d_model)
return cls_out, cit_out
def forward(self, x, text_global_emb, text_channel_embs):
"""
x: (B, C, T) - 時系列データ
text_global_emb: (B, text_dim) - サンプルテキスト埋め込み
text_channel_embs: (B, C, text_dim) - チャネルテキスト埋め込み
"""
# 時系列エンコード
cls_out, cit_out = self.encode_timeseries(x)
# テキスト射影
text_global = self.text_proj(text_global_emb)
B, C, _ = text_channel_embs.shape
text_channels = self.text_proj(
text_channel_embs.reshape(B * C, -1)
).reshape(B, C, -1)
# Cross-Attention による洗練 (簡易版)
cls_refined = self.cross_attn_global(cls_out)
cit_refined = self.cross_attn_channel(
cit_out.reshape(B * C, -1)
).reshape(B, C, -1)
return cls_refined, text_global, cit_refined, text_channels
これでモデルの定義が完了しました。次に、合成データを使った学習ループを実装し、損失が減少する様子を確認しましょう。
# ハイパーパラメータ
num_channels = 3
patch_len = 8
d_model = 64
num_heads = 4
ff_dim = 128
num_layers = 2
text_dim = 384
batch_size = 16
time_steps = 96
num_epochs = 100
lr = 1e-3
# モデルとオプティマイザ
model = SimpleTRACE(num_channels, patch_len, d_model, num_heads,
ff_dim, num_layers, text_dim)
criterion = BidirectionalInfoNCE(temperature=0.07, lambda_ch=0.5)
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=0.01)
# 学習ログ
losses_total = []
losses_global = []
losses_channel = []
for epoch in range(num_epochs):
model.train()
# 合成データ生成
# 各サンプルにランダムな周波数パターンを割り当て
freqs = torch.rand(batch_size, num_channels, 1) * 5 + 0.5
t = torch.linspace(0, 2 * np.pi, time_steps).unsqueeze(0).unsqueeze(0)
x = torch.sin(freqs * t) + 0.1 * torch.randn(batch_size, num_channels, time_steps)
# 合成テキスト埋め込み (周波数に基づく疑似埋め込み)
text_global_emb = torch.randn(batch_size, text_dim) * 0.1
text_global_emb[:, :num_channels] = freqs.squeeze(-1)
text_channel_embs = torch.randn(batch_size, num_channels, text_dim) * 0.1
for c in range(num_channels):
text_channel_embs[:, c, c] = freqs[:, c, 0]
# Forward + Loss
cls_ref, text_g, cit_ref, text_ch = model(x, text_global_emb, text_channel_embs)
loss, lg, lc = criterion(cls_ref, text_g, cit_ref, text_ch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses_total.append(loss.item())
losses_global.append(lg.item())
losses_channel.append(lc.item())
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1:3d} | Total: {loss.item():.4f} | "
f"Global: {lg.item():.4f} | Channel: {lc.item():.4f}")
# 損失推移の可視化
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
ax.plot(losses_total, label='統合損失', linewidth=2)
ax.plot(losses_global, label='サンプルレベル損失', linewidth=1.5, alpha=0.8)
ax.plot(losses_channel, label='チャネルレベル損失', linewidth=1.5, alpha=0.8)
ax.set_xlabel('エポック')
ax.set_ylabel('損失')
ax.set_title('簡易版TRACEの学習損失推移')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

上のグラフから、以下の特徴が読み取れます。
- すべての損失が減少し、モデルが正例と負例を区別する能力を獲得していることがわかります。破線は完全にランダムな予測の理論値 $\log(N) = \log(16) \approx 2.77$ です。サンプルレベル損失はこの破線付近から始まり、学習が進むとそれを下回ります。一番上の統合損失は両レベルの重み付き和($\mathcal{L}_{\text{global}} + 0.5\,\mathcal{L}_{\text{channel}}$)なので、より大きな値から始まります
- サンプルレベル損失とチャネルレベル損失の両方が同時に低下していることから、2つの粒度のアライメントが競合せず、相互に補完的に学習されていることがわかります
- チャネルレベル損失はサンプルレベル損失よりも高めに推移することが観察されます。これは、チャネルレベルの判別がより細かい粒度のタスクであり、難易度が高いためです。なお、この簡易デモは毎エポック新しい合成データを生成するため曲線にはノイズが乗りますが、全体としては明確な下降トレンドが見られます
検索デモ: テキストクエリで時系列を検索
学習済みモデルを使って、簡単な検索デモを実行します。
model.eval()
# テスト用の時系列データ: 3種類のパターン
patterns = {
'low_freq': torch.sin(0.5 * torch.linspace(0, 2*np.pi, time_steps)),
'mid_freq': torch.sin(2.0 * torch.linspace(0, 2*np.pi, time_steps)),
'high_freq': torch.sin(5.0 * torch.linspace(0, 2*np.pi, time_steps)),
}
# 3チャネルのテストサンプルを3つ作成
test_ts = torch.stack([
torch.stack([patterns['low_freq']] * 3), # サンプル0: 全チャネル低周波
torch.stack([patterns['mid_freq']] * 3), # サンプル1: 全チャネル中周波
torch.stack([patterns['high_freq']] * 3), # サンプル2: 全チャネル高周波
])
# テストサンプルのエンコード
with torch.no_grad():
cls_embs, cit_embs = model.encode_timeseries(test_ts)
cls_embs = F.normalize(cls_embs, dim=-1)
# クエリ: 疑似テキスト埋め込み (高周波パターンに対応)
query_emb = torch.randn(1, text_dim) * 0.1
query_emb[0, :3] = torch.tensor([5.0, 5.0, 5.0]) # 高周波の特徴
with torch.no_grad():
query_proj = F.normalize(model.text_proj(query_emb), dim=-1)
# コサイン類似度を計算
similarities = torch.matmul(query_proj, cls_embs.T).squeeze(0)
ranking = similarities.argsort(descending=True)
print("=== テキスト→時系列 検索結果 ===")
for rank, idx in enumerate(ranking):
names = ['low_freq', 'mid_freq', 'high_freq']
print(f" Rank {rank+1}: Sample {idx.item()} ({names[idx.item()]}), "
f"Similarity: {similarities[idx].item():.4f}")
この検索デモでは、「高周波パターン」を表す疑似テキストクエリに対して、高周波の時系列サンプルが最も類似度が高くランキングされることが確認できます。学習が不十分な場合やランダムなケースでは、正しいランキングが得られないこともありますが、十分なエポック数で学習すれば、対照学習により正しい対応付けが獲得されます。

学習後の埋め込み空間を主成分分析で2次元に落とすと、上の図のようになります。○が時系列、△がテキストで、色は周波数(=意味)のクラスを表します。各テキストクエリ△から最近傍の時系列○へ線を引くと、その大半が同じ意味クラス(緑)に正しく到達しており、検索が機能していることがわかります(このデモでの P@1 はおよそ0.79)。一点注意したいのは、時系列とテキストが完全に重なるのではなく、モダリティごとに別のかたまりを成している点です。これは modality gap と呼ばれる、CLIPなどのマルチモーダル対照学習で広く観察される現象で、「絶対位置が一致する」のではなく「相対的な近さの順序が一致する」ことで検索が成立していることを示しています。
CbAマスクの可視化
CbAのマスク構造を可視化して、チャネルごとの選択的なアテンションパターンを確認しましょう。
# CbAマスクの可視化
cba = ChannelBiasedAttention(d_model=64, num_heads=4)
num_ch = 3
patches_per_ch = 4 + 1 # CIT + 4 patches
seq_len = 1 + num_ch * patches_per_ch # CLS + channels
mask = cba.build_cba_mask(seq_len, num_ch, patches_per_ch)
fig, ax = plt.subplots(figsize=(8, 8))
im = ax.imshow(mask.float().numpy(), cmap='Blues', vmin=0, vmax=1)
# ラベルの作成
labels = ['[CLS]']
for c in range(num_ch):
labels.append(f'CIT_{c}')
for p in range(patches_per_ch - 1):
labels.append(f'P{c}_{p}')
ax.set_xticks(range(seq_len))
ax.set_yticks(range(seq_len))
ax.set_xticklabels(labels, rotation=90, fontsize=7)
ax.set_yticklabels(labels, fontsize=7)
ax.set_title('Channel-Biased Attention マスク\n(青=アテンション可, 白=ブロック)')
plt.colorbar(im, ax=ax, shrink=0.8)
plt.tight_layout()
plt.show()
このマスクの可視化から、CbAの構造が明確に読み取れます。
- [CLS]行(最上行)は全て青色(アテンション可能) — [CLS]トークンは全トークンにアテンションでき、サンプル全体の情報を集約します
- CIT行は、自チャネルの列だけが青色 — 例えば CIT_0 は [CLS], CIT_0, P0_0, P0_1, P0_2, P0_3 にのみアテンションし、チャネル1・2のトークンにはアテンションしません
- パッチ行は全て青色 — パッチトークンは制約を受けず、全トークンの情報にアクセスできます
この選択的なマスク構造により、CITはチャネル固有の「純粋な」表現を獲得できる一方、パッチトークンはチャネル間の相関関係も学習できるという、TRACEの中核的な設計が実現されています。
実験結果の解説
Chen et al. (2025) は、TRACEの有効性を複数のデータセットと下流タスクで評価しています。ここでは主要な実験結果とその意味を解説します。
検索性能
TRACEの最も重要な実験は、時系列とテキスト間のクロスモーダル検索です。評価指標は以下の通りです。
- P@1(Precision at 1): 検索結果の1位が正解である割合
- P@5(Precision at 5): 検索結果の上位5件に正解が含まれる割合
- MRR(Mean Reciprocal Rank): 正解の順位の逆数の平均
Weather データセットや TimeMMD ベンチマークにおいて、TRACE はText-to-Timeseries と Timeseries-to-Text の両方向の検索で state-of-the-art の性能を達成しています。特に、チャネルレベルの検索(特定のチャネルに対するテキストクエリ)では、既存手法を大幅に上回る精度を示しています。これは、CIT + CbA によるチャネルごとの独立した表現学習が有効に機能していることの証拠です。

論文の実測値で見ると、TRACEの検索精度は際立っています(上図)。左のクロスモーダル検索(テキスト→時系列)では、TRACEの P@1 は約89.6%で、時系列基盤モデルを流用したベースライン(Moment 64.7%、Timer-XL 63.9% など)を25ポイント以上引き離しています。右の時系列→時系列検索でも、TRACEの P@1 は0.900で、文脈メタデータを使う学習型のCTSR(0.682)や、DTW(0.380)・SAX-VSM(0.551)といった古典的な類似度手法を大きく上回ります。テキストとのアライメント学習が、検索に有利な「判別力の高い」埋め込みを生んでいることがわかります。
下流タスク: 予測と分類
TRACEの時系列エンコーダは、検索だけでなく、従来型のタスクにも高い性能を示しています。
予測(Forecasting): MAE(Mean Absolute Error)とMSE(Mean Squared Error)で評価した時系列予測タスクにおいて、TRACEのエンコーダに軽量な予測ヘッドを載せたモデルは、既存の時系列基盤モデルと同等以上の性能を達成しています。さらに、TRACE の検索機能を RAG コンポーネントとして利用した場合、予測精度がさらに向上することが報告されています。
分類(Classification): Accuracy と F1 スコアで評価した時系列分類タスクにおいても、TRACEは高い性能を示しています。これは、マルチモーダルアライメント学習が、時系列データのセマンティックに豊かな表現を獲得するのに寄与していることを示唆しています。

下流タスクでの効果を論文の数値で確認しましょう(上図)。左の予測では、TRACEで検索した類似事例をコンテキストとして与えると(RAG)、Timer-XLやTime-MoEなど複数の基盤モデルで予測MSEが低下します。特にテキストも合わせて与える「時系列+テキスト」設定で改善が顕著です。右のイベント分類では、TRACE単体(RAGなし)で85.2%、RAGを加えると89.8%まで向上し、DLinear(82.4%)やiTransformer(85.0%)といった専用モデルを上回ります。検索→文脈付与というRAGの枠組みが、予測・分類の両方を底上げできることを示す結果です。
アブレーション実験の解釈
Chen et al. (2025) は、TRACEの各コンポーネントの貢献を検証するために、詳細なアブレーション実験を行っています。
CITの効果: CITを除去すると、特にチャネルレベルの検索精度が大幅に低下します。CITがなければ、各チャネルの独立した表現を抽出する手段がなくなるため、チャネルごとのテキストとの対応付けが困難になるのです。
CbAの効果: CbAを通常のフルアテンションに置き換えると、CITの表現にチャネル間の情報が混入し、チャネルレベルの検索精度が低下します。CbAによる選択的な情報フローの制限が、チャネル表現の品質維持に不可欠であることがわかります。
Cross-Attentionの効果: Stage 2のCross-Attentionを除去すると、サンプルレベル・チャネルレベル双方の検索精度が低下します。Cross-Attentionは、時系列の表現をテキストの意味空間に適合させる「橋渡し」として重要な役割を果たしています。
Hard Negative Miningの効果: ハードネガティブを使わずにランダム負例のみで学習すると、検索精度が低下します。特に、チャネルレベルの intra-instance distractors(同一サンプル内の他チャネルを負例とする)の効果が大きく、これによりモデルがチャネル間の微妙な違いを学習できるようになっています。
これらのアブレーション結果は、TRACEの各設計要素が単独でも重要であり、かつ相互に補完的に作用していることを示しています。CIT、CbA、Cross-Attention、Hard Negative Mining のいずれを欠いても性能が低下するという事実は、このアーキテクチャの設計が慎重に練り上げられたものであることを裏付けています。
まとめ
本記事では、NeurIPS 2025で発表された TRACE (Chen et al., 2025) について解説しました。TRACEは、時系列データとテキストのマルチモーダルアライメントを実現するフレームワークであり、以下の技術的貢献を持ちます。
-
Channel Identity Token (CIT): 多変量時系列の各チャネルに固有のトークンを割り当て、チャネルレベルの独立した意味表現を可能にしました。これにより、「気温チャネルの急降下」のようなチャネルごとのテキスト記述との対応付けが実現されています
-
Channel-Biased Attention (CbA): CITのアテンションを同一チャネル内に制限するマスク機構により、チャネル間の意味的な混在を防止しています。パッチトークンは自由にチャネル間の情報を交換できるため、チャネル間の相関関係も学習可能です
-
2段階学習: MAEによる自己教師あり事前学習で時系列の構造的表現を獲得し、その後のマルチモーダルアライメントでテキストとの対応付けを学習する2段階アプローチにより、安定した学習が実現されています
-
サンプルレベル + チャネルレベルの対照学習: 粗い粒度と細かい粒度の2層構造の InfoNCE 損失により、時系列データの多層的な意味をテキストと結びつけています
-
Hard Negative Mining: サンプルレベルとチャネルレベルの両方で、特に intra-instance distractors(同一サンプル内の他チャネル)をハードネガティブとして活用することで、チャネル間の微妙な違いの学習を促進しています
今後の展望
TRACEは、時系列データの「セマンティック接地」という重要な問題に対して、説得力のある解決策を提示しました。今後は以下のような発展が期待されます。
時系列基盤モデルとの統合: 既存の時系列基盤モデル(TimesFM, Chronos 等)にTRACEのマルチモーダルアライメント機構を統合することで、予測・分類・検索を一体的に行える統合モデルが構築できる可能性があります。時系列基盤モデルの全体像については、以下の記事で詳しく解説しています。
より多くのドメインへの展開: 現在は気象データやTimeMMDベンチマークが中心ですが、医療(電子カルテの時系列 + 診断テキスト)、製造業(センサデータ + 故障報告テキスト)、金融(市場データ + ニューステキスト)など、テキストと時系列のペアが自然に存在する多くのドメインへの展開が期待されます。
3つ以上のモダリティの統合: 時系列とテキストの2つのモダリティに加えて、画像(例: 気象衛星画像)や音声など、より多くのモダリティとのアライメントへの拡張も考えられます。
次のステップとして、以下の記事も参考にしてください。