Level 1概要役割と処理の流れ
何をする機能か
学習時に、Decoder へ入力する系列を作ります。
正解の出力文を1 つ右へずらし、先頭に開始記号(<bos> や <s>)を置いたものです。
正解(教師信号): keys on the table are lost . </s>
Decoder への入力: <s> keys on the table are lost .同じ列を縦に並べると、「各位置の入力に対する正解は、1 つ先のトークン」という対応になります。
| 位置 | Decoder 入力 | その位置で予測すべき正解 |
|---|---|---|
| 0 | <s> | keys |
| 1 | keys | on |
| 2 | on | the |
| 3 | the | table |
| 4 | table | are |
| 5 | are | lost |
なぜ必要か
Decoder は「次のトークンを予測する」モデルです。 学習では、系列のすべての位置について同時に予測を計算し、まとめて損失を取ります。
このとき、位置 の入力が「 番目までの確定済みトークン」、 正解が「 番目のトークン」という形にそろっていないと、 1 回の順伝播で全位置ぶんの予測を学習できません。右シフトはこの形を作る操作です。
この方式は Teacher Forcing(教師強制) と呼ばれます。 モデル自身の予測が間違っていても、次の位置には正解を入力するため、 誤りが連鎖せず学習が安定します。
全体の中でいつ呼ばれるか
Decoder 側の最初、出力埋め込み の直前です。 学習時のみの処理で、推論時には行いません(下の節で説明します)。
Level 2内部処理Tensor形状・内部ステップ・設計上の意味
前段から受け取るもの
正解の出力文をトークン化した target_ids [B, T]。
後段へ渡すもの
decoder_input_ids [B, T]。出力埋め込み → 位置情報 →
マスク付き自己注意 と流れます。
同時に、損失計算用の labels(1 つ左にずらしたもの、つまり元の正解列)を保持します。
例文での処理
共通例文
The keys on the table are lost.
概念上のトークン列(S = 8): [The, keys, on, the, table, are, lost, .]
この例文を出力側の系列として扱うと、次のようになります。
- <s>
- The
- keys
- on
- the
- table
- are
- lost
- The
- keys
- on
- the
- table
- are
- lost
- .
位置 5(入力が table)に注目すると、
そこまでに見えているのは <s> The keys on the table で、予測すべきは are です。
are を正しく予測するには、直前の table(単数)ではなく
主語の keys(複数)を参照する必要があります。
この参照を担うのが マスク付き自己注意 です。
学習時と推論時の違い
ここが最も混同しやすい点です。
| 学習時 | 推論時(生成) | |
|---|---|---|
| Decoder 入力 | 正解を右シフトした系列(全位置を一度に) | これまでに自分が生成したトークン列 |
| 実行回数 | 1 回の順伝播で全位置を計算 | 1 トークンにつき 1 回、逐次的に実行 |
| 誤りの扱い | 誤っても次は正解が入力される | 誤るとその誤りが次の入力になる(誤りが連鎖する) |
| 右シフト操作 | 行う | 行わない(生成済み列がそのまま入力) |
学習時と推論時で入力の作り方が違うことを exposure bias と呼び、 生成品質の議論でよく取り上げられます。
推論時は、先頭の <s> だけを入力して 1 トークン目を得て、
それを末尾に足して再入力する、という手順を繰り返します。
詳しくは 次トークンの確率 を参照してください。
EncoderとDecoderでの違い
Encoder 側にはこの処理がありません。入力文は最初から全部わかっており、 「次を予測する」形にする必要がないためです。
Decoder-only の LLM でも同じ考え方が使われます。 プロンプトと応答をつなげた 1 本の系列に対し、 入力を 1 つずらしたものを正解として学習します。
入力と出力
入力
| 項目名 | データの意味 | データ型 | Tensor形状 | 記号の意味 | 具体例 | 次の渡し先 |
|---|---|---|---|---|---|---|
target_ids | 正解の出力文のトークンID | int64 Tensor | [B, T] | B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) | [[The, keys, on, the, table, are, lost, .]] のID列 | 右シフト処理 |
bos_id | 開始記号のトークンID | int | — | — | 1(<s>) | 先頭に挿入 |
出力
| 項目名 | データの意味 | データ型 | Tensor形状 | 記号の意味 | 具体例 | 次の渡し先 |
|---|---|---|---|---|---|---|
decoder_input_ids | 1つ右へずらし、先頭に開始記号を置いた系列 | int64 Tensor | [B, T] | B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) | [[<s>, The, keys, on, the, table, are, lost]] のID列 | 出力埋め込み(Token Embedding) |
labels | 各位置で予測すべき正解(損失計算に使う) | int64 Tensor | [B, T] | B = バッチサイズ(同時に処理する文の数) / T = 出力系列長(Decoder側のトークン数) | 元の target_ids と同じ | 交差エントロピー損失 |
B- バッチサイズ(同時に処理する文の数)
T- 出力系列長(Decoder側のトークン数)
このサイトでは Tensor 形状を PyTorch の `batch_first=True` 相当、つまり `[B, S, D]`(バッチ次元が先頭)で統一しています。実装によっては `[S, B, D]` を採用するものもあります。
よくある誤解
Level 3数式・Python数式・疑似実装・計算量・実装差
Pythonで表したインターフェース
from dataclasses import dataclass
import torch
from torch import Tensor
@dataclass
class DecoderTrainingBatch:
decoder_input_ids: Tensor # [B, T] 右シフト後。Decoder へ入力する
labels: Tensor # [B, T] 各位置の正解
def shift_right(target_ids: Tensor, bos_id: int, pad_id: int = 0) -> Tensor:
"""正解列を1つ右へずらし、先頭に開始記号を置く。
Args:
target_ids: [B, T] int64。正解の出力トークン列。
bos_id: 開始記号のトークンID。
pad_id: パディングID(末尾で押し出された分の埋めには使わない)。
Returns:
[B, T] int64。Decoder への入力列。末尾の1トークンは押し出されて消える。
"""
batch_size, _ = target_ids.shape
bos = torch.full((batch_size, 1), bos_id, dtype=target_ids.dtype)
# [B, 1] と [B, T-1] を連結して [B, T] に戻す
return torch.cat([bos, target_ids[:, :-1]], dim=1)
def build_training_batch(target_ids: Tensor, bos_id: int) -> DecoderTrainingBatch:
"""学習用の (Decoder入力, 正解) の組を作る(Teacher Forcing)。"""
return DecoderTrainingBatch(
decoder_input_ids=shift_right(target_ids, bos_id),
labels=target_ids,
)
# 最小使用例
BOS = 1
target_ids = torch.tensor([[5, 6, 7, 8, 9, 10, 11, 12]]) # [B=1, T=8]
batch = build_training_batch(target_ids, bos_id=BOS)
print(batch.decoder_input_ids) # tensor([[ 1, 5, 6, 7, 8, 9, 10, 11]])
print(batch.labels) # tensor([[ 5, 6, 7, 8, 9, 10, 11, 12]])
print(batch.decoder_input_ids.shape, batch.labels.shape) # [1, 8] [1, 8] == [B, T]
# 位置 t の入力に対する正解は、必ず 1 つ先のトークンになっている
t = 4
print(batch.decoder_input_ids[0, t + 1] == batch.labels[0, t]) # tensor(True)この例で単純化している点
- パディングと損失のマスク(ignore_index)は最小限にとどめています。
- 実装によっては labels 側を左シフトする(入力はそのまま)方式を採ります。結果の対応関係は同じです。
- 推論時の生成ループは next-token-prediction のページで扱います。
型と Tensor 形状の見え方を掴むための擬似実装です。そのまま学習・推論に使えるものではありません。
損失の計算
全位置の予測 logits [B, T, V] と labels [B, T] から、
交差エントロピーをまとめて計算します。
が「 より前の正解トークン」であることが Teacher Forcing の要点です。 モデル自身の予測ではありません。
パディング位置は損失から除外します(PyTorch では ignore_index を使います)。
実装差
- 開始記号:
<s><bos>[CLS]<pad>など、モデルによって異なります。 T5 は<pad>を開始記号として使います。 - シフトの向き: 入力を右シフトする代わりに、labels を左シフトする実装もあります
(
logits[:, :-1]とlabels[:, 1:]を突き合わせる書き方)。対応関係は同じです。 - Decoder-only LLM: プロンプト部分の位置を損失から除外し、 応答部分だけで学習することがよくあります(instruction tuning)。