[Medical AI with Python:P22.2] Transformerの全体構造と中身

  • URLをコピーしました!
目次

はじめに

ChatGPTやBERT、さらには医療分野に特化したBioBERTやClinicalBERTなど、今の自然言語処理(NLP)の基盤には、ほぼ例外なくこの「Transformer」というアーキテクチャが使われています。

もともとは2017年にGoogleが提案したモデルで、「Attention is All You Need」というタイトルの論文で一躍注目を浴びました。それまでのRNNやLSTMとは全く異なるアプローチで、テキストの文脈を一度に広く捉えられることが大きな特徴です。その結果として、翻訳、要約、分類、質問応答といったあらゆる言語タスクで飛躍的な性能向上をもたらしました。

医療の現場でも例外ではありません。たとえば、患者の診療記録(カルテ)を要約するAIや、研究論文から特定の情報を抽出するAIなど、実際の応用が進んでいます。こうした技術を“使える”医療者・研究者になるためには、単なる理論理解だけでなく、実際にモデルの仕組みを分解して、自分の手で再構築できるようになることが理想です。

そこで本章では、Transformerの中でも特に重要な構成要素である「層」の内部構造について、図解と数式を交えながら順を追って解説していきます。少し技術的な話にはなりますが、ここを理解すると、生成系AIがどのように意味をとらえ、文を生成しているのかが一気に見えてきます。

今回取り上げるのは、以下の5つの要素です:

構成要素役割の概要
Self-Attention / Multi-Head Attention文中の他の単語と文脈的に関連づける
Feed Forward Network(FFN)各トークンの表現を非線形変換によって抽象化・強化
残差接続(Residual Connection)入力と出力を直接つなぐことで学習を安定化
正規化(Layer Normalization)ネットワーク全体の勾配を安定させ、収束を促進
位置符号化(Positional Encoding)単語の並び順をベクトルに埋め込むための仕組み

なお、以下のように図式化すると、Transformerの層構造が全体としてどのような流れで処理されるのかを把握しやすくなります。

このように、Transformerの各層は、Attentionによって他の語との関係を捉え、FFNで各単語の意味を深め、それを残差接続と正規化で安定的に学習できるように設計されています。

次の節では、まずこの中でも心臓部とも言える「Self-Attention」の仕組みを、数式とともに丁寧に掘り下げていきます。

Transformerの全体構造:2つの主要なブロック

Transformerというモデルは、シンプルに見えて、実は非常に巧妙な構造を持っています。全体としては、大きく2つのブロックに分かれています。

1つは「エンコーダ(Encoder)」、もう1つは「デコーダ(Decoder)」です。この構造、自然言語処理の世界ではまるで「聞き手」と「話し手」のような関係にあると言えるかもしれません。

エンコーダは、与えられた入力文を意味的に圧縮して表現する役割を果たします。例えば、ある論文の1文を読み込んで、それが「糖尿病の初期治療にメトホルミンが推奨される」という意味を持っていることを、抽象的なベクトルで表現するのがこの部分です。

一方、デコーダは、その「意味のベクトル」をもとに、新しい文を生成する働きを担います。たとえば、「その治療方針を患者に説明する文章を日本語で出力する」といった場面で使われます。GPTのような生成型AIは、実はこの「デコーダ部分」だけを使って構成されています。

今回のテーマはこのTransformerの中でも、層(Layer)ごとの内部構造がどのように機能しているのかを掘り下げていくことにあります。

以下に、全体の構造を簡略化した図を示します。

この図の中で特に注目していただきたいのは、エンコーダ・デコーダそれぞれが「N層」重ねられているという点です。そして、各層はすべて同じ構造をしているというのも、Transformerの特徴です。つまり、1層を理解できれば、全体の構造も見通しが立つようになります。

次の章では、その「1層」の中で最も重要なパーツ、Self-Attention(自己注意)とは何なのかを、数式と図を使いながらじっくりと解説していきます。

入力処理:埋め込みと位置符号化

Transformerに文章を入力する前には、必ず行わなければならない重要な前処理があります。それが「埋め込み(Embedding)」と「位置符号化(Positional Encoding)」です。

ちょっと想像してみてください。人間なら「糖尿病の治療法は?」という問いかけを聞けば、単語の意味や順序から、ある程度の文脈をくみ取ることができます。でも、AIにとっては「糖尿病」も「治療法」も、最初はただの文字列に過ぎません。それを処理可能な“数値のかたまり”へと変換するのが、埋め込みの役割です。

埋め込みとは、各単語や記号に対応するベクトル(多次元の数値列)を与える処理のことです。言い換えると、「単語の意味を数学的に表現する」工程です。さらにTransformerでは、単語の“並び順”の情報も扱う必要があるため、そこに「位置情報」を加えることも不可欠です。

この2つを合わせることで、Transformerは文の構造や意味の流れを理解できるようになります。以下は、その処理を実際に日本語BERTで試してみるコード例です。

# 日本語のTransformerモデル(BERT)で、入力文をトークン化し、ベクトル化する処理

import torch
from transformers import BertTokenizer, BertModel

# Step 1: 日本語BERTのトークナイザーを読み込む(cl-tohokuの事前学習済みモデルを利用)
tokenizer = BertTokenizer.from_pretrained("cl-tohoku/bert-base-japanese")

# Step 2: 入力文を定義(今回は医療の例:「糖尿病の治療法は?」)
input_text = "糖尿病の治療法は?"

# Step 3: 入力文をトークン化してテンソル形式に変換
# この時点で位置情報なども内部的に追加されます
inputs = tokenizer(input_text, return_tensors="pt")

# Step 4: BERTモデルをロード(事前学習済みの日本語BERT)
model = BertModel.from_pretrained("cl-tohoku/bert-base-japanese")

# Step 5: モデルに入力し、トークンごとの出力ベクトル(埋め込み+文脈情報)を取得
outputs = model(**inputs)

# Step 6: 出力ベクトルの形状を確認
# 出力テンソルは:[バッチサイズ, トークン数, 隠れ層の次元数] の構造
print(outputs.last_hidden_state.shape)

例えばこのコードを実行すると、以下のような形のテンソル(3次元の数値のかたまり)が得られます:

torch.Size([1, 9, 768])

これは、「バッチサイズ1」「トークン数9」「1トークンあたりのベクトルが768次元」という意味です。

埋め込みと位置符号の役割を図で理解する

このように、埋め込みと位置符号化を経て、初めてTransformerは「文の構造」や「意味の流れ」を捉えられるようになります。

余談ですが、位置符号化の手法にはいくつかのバリエーションがあり、標準的なものはサイン波とコサイン波の関数で位置情報を埋め込みます(これについては、以下の「Deep Dive! 位置符号化をもっと知る!」をご参照ください)。

Deep Dive! 位置符号化をもっと知る!

Transformerモデルが、まるで人間のように自然な文章を生成したり、複雑な質問に的確に答えたりできるのはなぜでしょうか?その秘密の一つに、今回深掘りする「位置符号化(Positional Encoding)」という、一見地味ながらも極めて重要な仕組みがあります。このセクションでは、位置符号化がなぜ必要なのか、どのように機能するのか、そして医療分野でどのような意味を持つのかを、Pythonコードを交えながら徹底的に解説します。

1. なぜTransformerに「位置情報」が絶対に不可欠なのか?

Transformerの心臓部である「Self-Attention機構」は、文中の各単語が他のどの単語に注目すべきかを計算します。このとき、Self-Attentionは文中の全ての単語を同時並列的に処理します。これは計算効率の観点からは大きな利点ですが、副作用として単語の順序情報が失われてしまうという重大な欠点を抱えています。これを「順序不変性(Order Invariance)」と呼びます。

しかし、私たちが日常で使う言語において、語順は意味を決定づける極めて重要な要素です。例えば、医療現場における以下の2つの文を考えてみてください。

  • 「薬剤Aを投与後、症状Bが改善した。」
  • 「症状Bが改善後、薬剤Aを投与した。」

これらの文は構成する単語(トークン)はほぼ同じですが、語順が異なるため、医学的な意味合い(因果関係や時系列)が全く逆転してしまいます。Self-Attention機構だけでは、この致命的な違いを区別できません。そこで登場するのが「位置符号化」です。位置符号化は、各単語の「文中での位置(順番)」という情報を、モデルが理解できる数値的な形で与える役割を担います。

2. 位置符号化の核心アイデア:埋め込みベクトルへの「位置タグ」の付与

Transformerでは、まず入力文の各単語(トークン)を「単語埋め込み(Word Embedding)」というプロセスで数値ベクトルに変換します。このベクトルは、その単語が持つ意味的な情報を表現しています(例:「発熱」と「悪寒」は意味的に近いため、ベクトル空間上でも近い位置に配置される)。

位置符号化の基本的なアイデアは、この単語埋め込みベクトルに対して、その単語が文中の何番目に現れるかを示す「位置ベクトル(Positional Vector)」を加算するというものです。これにより、元々の単語の意味情報に加えて、その単語の位置情報も組み込まれた新しいベクトルが生成されます。この新しいベクトルが、Transformerの各層で処理される実際の入力となります。

入力トークン → 単語埋め込みベクトル(意味情報)
                  ↓
          位置情報 → 位置ベクトル(順序情報)
                  ↓ 加算
    最終的な入力ベクトル(意味情報+順序情報)→ Transformer層へ

例えるなら、各単語に「意味のIDカード(単語埋め込み)」と「順番を示す座席番号札(位置ベクトル)」を渡し、その両方の情報を使ってAIが文全体を理解するイメージです。

3. 代表的な位置符号化:三角関数(sin/cos)を用いた巧妙な方法

さて、Transformerが単語の「順番」をどうやって覚えているのか、その核心に迫っていきましょう。実は、オリジナルのTransformer論文「Attention is All You Need」[1]では、三角関数、つまりサイン(sin)とコサイン(cos)を使った、実に巧妙なアイデアが提案されているんです。数学で習ったあの三角関数が、まさかAIの言語理解にこんな形で役立っているなんて、ちょっと驚きですよね。でも、なぜわざわざ三角関数なのでしょうか?この選択には、いくつかの納得のいく理由があるんですよ。

主なメリットを整理してみると、こんな感じです。

メリット詳しい説明
① 決定論的で学習が不要位置符号が固定の数式で計算されるため、モデルを訓練してパラメータを学習する必要がありません。これは、学習プロセスをシンプルにし、計算コストを抑えることに繋がります。毎回同じ入力に対しては必ず同じ位置符号が生成されるので、モデルの動作が安定するという利点もあります。
② 未知の文長への対応力理論上は、訓練データに含まれていなかったような非常に長い文章に対しても、三角関数を使えば位置符号を生成し続けることができます。もちろん、あまりにも長くなると性能が落ちてくる可能性はありますが、学習時に固定長の位置埋め込みを用意する方法に比べて、柔軟性があると言えるでしょう。
③ 相対的な位置関係の表現これは三角関数の面白い特性なのですが、ある位置 pos の符号と、そこから一定距離 k だけ離れた位置 pos+k の符号の間には、数学的に見て線形的な関係性(ある種のパターン)が現れます。このおかげで、モデルは「2つ隣の単語」とか「3つ前の単語」といった相対的な位置関係を捉えやすくなるのでは、と考えられています。これが後のSelf-Attention機構で文脈を理解する際に、地味に効いてくるんですね。
④ 各位置にユニークな「指紋」文中のそれぞれの単語の位置(1番目、2番目…)に対して、異なるユニークな位置符号ベクトルが割り当てられます。これにより、モデルが「この単語は文頭に近いのか、それとも文末の方なのか」といった絶対的な位置情報も区別できるようになります。各単語に、その位置を示す固有のIDタグが付与されるようなイメージですね。

では、いよいよ具体的な数式を見ていきましょう。位置 \( pos \) にあるトークン(単語やサブワードのことですね)の、埋め込みベクトルの特定の次元 \( i \) に対する位置符号 \( PE \) は、次のように定義されています。ここで、\( d_{\text{model}} \) は単語埋め込みベクトルの総次元数を表します。多くのBERTベースのモデルでは、この \( d_{\text{model}} \) は768次元や1024次元といった値を取ります。

位置符号ベクトル \(PE\) の \( (pos, 2i) \) 番目の要素(つまり偶数番目の次元)は:

\[ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

そして、\( (pos, 2i+1) \) 番目の要素(つまり奇数番目の次元)は:

\[ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

ちょっと数式が出てきて面食らうかもしれませんが、大丈夫です。一つ一つの記号が何を表しているのか、一緒に見ていきましょう。

  • \( pos \): これはトークンの「文中の位置」を示すインデックスです。プログラミングの世界ではよくあるように、0から始まります。つまり、文の最初のトークンは \( pos = 0 \)、2番目は \( pos = 1 \)、といった具合です。
  • \( i \): こちらは、トークンを表す \( d_{\text{model}} \) 次元のベクトルの、「何番目の次元か」を示すインデックスです。これも0から始まり、おおよそ \( d_{\text{model}}/2 – 1 \) までの値を取ります。数式を見ると、\( 2i \) と \( 2i+1 \) でペアになっていて、偶数次元にはサイン関数、奇数次元にはコサイン関数を適用しているのが分かりますね。
  • \( d_{\text{model}} \): これは先ほども触れましたが、モデルが扱うベクトルの次元数です。例えば、768次元なら、各トークンは768個の数値の組で表現されるわけです。位置符号のベクトルも、この次元数に合わせます。
  • \( 10000 \): この「10000」という数字は、論文の著者が設定したハイパーパラメータ(モデルの挙動を調整するための定数)です。この値を変えることで、サイン波やコサイン波の「波長」のスケール感を調整する役割があります。なぜ10000なのか?というのは、経験的に良い結果が得られた値の一つ、と捉えておくのが良いかもしれません。

さて、この数式の「キモ」となるのは、分母に含まれている \( 10000^{2i/d_{\text{model}}} \) の部分です。これが、サイン関数やコサイン関数の引数 \( \frac{pos}{\text{分母}} \) の中で、波の周期、つまり「波長」をコントロールしているんです。どういうことか、もう少し詳しく見てみましょう。

この分母の値を \( W_i = 10000^{2i/d_{\text{model}}} \) と置くと、サイン関数やコサイン関数の中身は \( pos/W_i \) と書けますね。この \( W_i \) が、実は \( i \) の値(つまりベクトルの次元のインデックス)によって変化するんです。

  • 次元インデックス \( i \) が小さいとき(ベクトルの”前半”の次元): このとき、指数部分の \( 2i/d_{\text{model}} \) は小さな値になります。すると、\( W_i = 10000^{\text{小さい値}} \) も比較的小さな値になります。その結果、\( pos/W_i \) は \( pos \) の変化に対して敏感に、つまり値が大きく変動します。これは、サイン波やコサイン波で言うと、波長が短く、周波数が高い状態を意味します。これらの次元は、トークンの位置が少し変わるだけでも値が大きく変わるので、細かい位置の変化を捉えるのに役立つと考えられます。
  • 次元インデックス \( i \) が大きいとき(ベクトルの”後半”の次元): 逆に \( i \) が大きくなると、指数部分の \( 2i/d_{\text{model}} \) は1に近い値(最大でほぼ1)になります。すると、\( W_i = 10000^{\text{大きい値}} \) は非常に大きな値になります。その結果、\( pos/W_i \) は \( pos \) が多少変化しても、値の変動は緩やかになります。これは、サイン波やコサイン波で言うと、波長が長く、周波数が低い状態です。これらの次元は、トークンの大まかな位置(文の前の方か、後ろの方かなど)を捉えるのに適していると言えるでしょう。

言葉だけだと少しイメージしづらいかもしれませんね。ここで、簡単なテキストベースの図で、周波数の違いを表してみましょう。

位置符号ベクトルの次元と波長(周波数)の関係 次元インデックス (i) トークンの位置 (pos) i が小さい (例: 0, 1) : 高周波数(細かい波) 次元 0 (sin波) posの変化に敏感 次元 1 (cos波) posの変化に敏感 … (中間の次元) … i が大きい (例: d_model/2 -1) : 低周波数(ゆるやかな波) 次元 d_model-2 (sin波) posの変化に鈍感 次元 d_model-1 (cos波) posの変化に鈍感 ※ この図は、位置符号化ベクトルが次元 (i) とトークン位置 (pos) によってどのように変化するかの概念を示しています。 ※ 高周波の次元はトークン位置の小さな変化を、低周波の次元は大きな変化を捉えます。
【位置符号ベクトルの次元と波長(周波数)の関係イメージ】

トークンの位置 (pos) →
次元インデックス (i)
↓
--------------------------------------------------------------------
i が小さい (例: 0, 1)  [高周波数:細かい波]
  次元0 (sin):  0 + - 0 + - 0 + - 0 + - 0  (posの変化に敏感)
  次元1 (cos):  + 0 - + 0 - + 0 - + 0 - +  (posの変化に敏感)
--------------------------------------------------------------------
... (中間の次元) ...
--------------------------------------------------------------------
i が大きい (例: d_model/2 -1) [低周波数:ゆるやかな波]
  次元d_model-2 (sin): 0   +   +   +   0   -   -   -   0 (posの変化に鈍感)
  次元d_model-1 (cos): +   +   0   -   -   -   0   +   + (posの変化に鈍感)
--------------------------------------------------------------------
※ + は正の値、- は負の値、0 はゼロ付近を大まかに示しています。
※ 実際には連続的なsin/cosカーブですが、ここでは離散的な位置での変化をイメージしています。

この図で何となく伝わるでしょうか?ベクトルの前半の次元(\(i\)が小さい)では、位置(\(pos\))が1つ変わるだけでもサイン・コサインの値がコロコロ変わるのに対し、後半の次元(\(i\)が大きい)では、位置が多少変わっても値の変化はゆっくりです。まるでオーケストラの楽器が、高い音域(ヴァイオリンなど)から低い音域(コントラバスなど)まで、それぞれの役割分担で豊かなハーモニーを生み出すのに似ているかもしれませんね。

このようにして、Transformerは各単語の位置に対して、その次元ごとに異なる周期(周波数)のサイン波とコサイン波を割り当てます。そして、これら多数の異なる周期の波の値を組み合わせることで、各位置に対してユニーク(唯一無二)なベクトル値を生成するのです。さらに、この設計のおかげで、モデルは単語間の相対的な位置関係も学習しやすくなると考えられています。この辺りは、数学的な美しさすら感じさせる部分だと個人的には思っています。

どうでしょうか、三角関数を使った位置符号化の仕組み、少しはイメージが湧いてきましたか?次のセクションでは、この数式を実際にPythonコードで実装してみて、目で見てその挙動を確かめてみましょう。

4. Pythonで位置符号化を実装してみよう:手を動かして理解を深める

さて、ここまで数式とにらめっこしながら理論を追ってきましたが、やっぱり「百聞は一見に如かず」ですよね。実際に自分の手でコードを動かしてみるのが、一番理解が深まる近道だったりします。そこでこのセクションでは、先ほどの三角関数を使った位置符号化の計算を、Pythonというプログラミング言語を使って実装してみましょう。特別な深層学習ライブラリ(例えばPyTorchやTensorFlow)がなくても、数値計算の定番ライブラリであるNumPyと、グラフ描画でおなじみのMatplotlibがあれば大丈夫です。もちろん、本格的なTransformerモデルではPyTorchなどが使われますが、基本的な計算ロジックは同じなので、ここでしっかり掴んでおきましょう。

まずは、位置符号化行列を生成する関数を作ってみます。この関数は、想定される文の最大の長さ(max_seq_len)と、単語埋め込みベクトルの次元数(d_model)を引数に取って、例のサインとコサインの計算を実行してくれます。

# 必要なライブラリをインポートします
# NumPyは、Pythonで数値計算(特に配列や行列の操作)を効率的に行うための定番ライブラリです。
import numpy as np
# Matplotlibは、グラフを描画するためのライブラリです。今回は計算結果を視覚的に確認するために使います。
import matplotlib.pyplot as plt

def get_positional_encoding(max_seq_len, d_model):
    """
    Transformerで用いられるサイン/コサイン関数による位置符号化を計算する関数。

    Args:
        max_seq_len (int): 想定される最大のシーケンス長(文の最大トークン数)。
                           例えば50なら、50個のトークンまでの文に対応できます。
        d_model (int):     単語埋め込みベクトルおよび位置符号ベクトルの次元数。
                           例えば128なら、各トークン・各位置が128個の数値で表現されます。

    Returns:
        numpy.ndarray:     計算された位置符号化行列。
                           形状は (max_seq_len, d_model) になります。
                           各行が特定の位置の符号ベクトル、各列が特定の次元に対応します。
    """

    # STEP 1: 位置符号化行列を格納するための「器」を用意します。
    #         最初は全ての要素が0のNumPy配列として初期化します。
    #         行数が max_seq_len、列数が d_model です。
    positional_encoding = np.zeros((max_seq_len, d_model))

    # STEP 2: 各「位置」 (pos) について、0から max_seq_len-1 までループします。
    #         これが数式中の 'pos' にあたります。
    for pos in range(max_seq_len):
        # STEP 3: 各「次元のペア」 (i) についてループします。
        #         偶数次元 (2i) と奇数次元 (2i+1) をセットで計算するため、
        #         ループのステップは2ずつ進みます (0, 2, 4, ..., d_model-2)。
        #         これが数式中の 'i' (の2倍) にあたります。
        for i in range(0, d_model, 2):
            # STEP 3a: サイン関数とコサイン関数の分母部分を計算します。
            #          数式: 10000^(2i / d_model)
            #          np.power(基数, 指数) でべき乗を計算します。
            denominator = np.power(10000, (2 * i) / d_model)

            # STEP 3b: 偶数番目の次元 (列インデックス i) にサイン関数を適用します。
            #          数式: sin(pos / denominator)
            #          positional_encoding[pos, i] は、pos行目(位置pos)、i列目(次元2i)の要素を指します。
            positional_encoding[pos, i] = np.sin(pos / denominator)

            # STEP 3c: 奇数番目の次元 (列インデックス i+1) にコサイン関数を適用します。
            #          数式: cos(pos / denominator)
            #          ただし、d_modelが奇数の場合、i+1が配列の範囲外になる可能性があるので、
            #          範囲内かチェックしてから代入します。
            if i + 1 < d_model:
                 positional_encoding[pos, i + 1] = np.cos(pos / denominator)

    # STEP 4: 計算が終わった位置符号化行列を返します。
    return positional_encoding

# --- ここから、上記関数を使って実際に計算し、可視化します ---

# パラメータを設定してみましょう(お好みで変更してみてください)
max_sequence_length = 50  # 例えば、最大50トークンからなる文を処理すると仮定します。
embedding_dimension = 128 # 例えば、各トークンを128次元のベクトルで表現すると仮定します。

# 上で定義した関数を呼び出して、位置符号化行列を実際に計算します。
pe_matrix = get_positional_encoding(max_sequence_length, embedding_dimension)

# 計算結果の行列の形状(何行何列か)を確認してみましょう。
# f-stringを使って、変数と文字列を組み合わせて表示しています。
print(f"生成された位置符号化行列の形状: {pe_matrix.shape}")
# 期待通り (50, 128) と表示されればOKです。

# --- 次に、計算した位置符号化行列をグラフで見てみましょう ---
# これにより、数値の羅列だけでは分かりにくいパターンを視覚的に捉えられます。

# グラフの描画領域のサイズを指定します (横10インチ, 縦8インチ)。
plt.figure(figsize=(10, 8))

# pcolormeshという関数を使って、行列の値を色の濃淡(ヒートマップ)で表示します。
# pe_matrix: 表示する行列データ。
# cmap='viridis': 色のテーマを指定します。'viridis'はよく使われる見やすいカラーマップです。
plt.pcolormesh(pe_matrix, cmap='viridis')

plt.xlabel('Embedding Dimension (ベクトルの次元)') # X軸のラベルを設定します。
plt.ylabel('Position in Sequence (文中のトークン位置)') # Y軸のラベルを設定します。
plt.title('Positional Encoding Matrix (位置符号化行列の可視化)') # グラフのタイトルを設定します。
plt.colorbar() # カラーバー(値と色の対応を示す凡例)を表示します。
plt.show() # 作成したグラフを画面に表示します。

# --- 最後に、簡単な日本語の文でどう使われるかのイメージです ---
# 実際のTransformerモデルの入力処理では、以下のような流れになります:
# 1. テキストをトークン(単語やサブワード)に分割する。
# 2. 各トークンをトークンID(数値)に変換する。
# 3. 各トークンIDを「単語埋め込みベクトル」に変換する(これは通常、学習済みの重み)。
# 4. この単語埋め込みベクトルに、今回計算した「位置符号化ベクトル」を加算する。
# この4番目のステップをここでシミュレートしてみます。

# 例として、短い日本語の文を考えます。
# 簡単のため、ここでは単語単位で分割(トークン化)したとします。
japanese_tokens = ["今日", "の", "天気", "は", "晴れ", "です", "。"]
num_tokens = len(japanese_tokens) # この文のトークン数は7ですね。

# 本来は学習済みの単語埋め込みを使いますが、ここではランダムな値で仮の単語埋め込みベクトルを生成します。
# np.random.rand(行数, 列数) で、0から1までの一様乱数で指定した形状の行列を作ります。
# 形状: (この文のトークン数, 埋め込みベクトルの次元数)
word_embeddings = np.random.rand(num_tokens, embedding_dimension)
print(f"\n仮の単語埋め込み行列の形状: {word_embeddings.shape}")

# 先ほど計算した pe_matrix (50トークン分の位置符号化) から、
# この文に必要な部分 (最初の num_tokens トークン分) だけを取り出します。
# NumPyのスライス機能を使います: pe_matrix[開始行:終了行, 開始列:終了列]
# :num_tokens は 0行目から num_tokens-1 行目まで、という意味です。
# : は全ての列、という意味です。
positional_encodings_for_sentence = pe_matrix[:num_tokens, :]
print(f"この文に対応する位置符号化行列の形状: {positional_encodings_for_sentence.shape}")

# 単語埋め込みベクトルと位置符号化ベクトルを要素ごとに加算します。
# これがTransformerの最初の層への最終的な入力ベクトルになります。
final_input_vectors = word_embeddings + positional_encodings_for_sentence
print(f"最終的な入力ベクトルの形状 (単語埋め込み + 位置符号化): {final_input_vectors.shape}")

# 最初のトークン「今日」について、処理の前後を少し詳しく見てみましょう。
# :5 は、ベクトルの最初の5次元分だけ表示するという意味です(全部表示すると長すぎるので)。
print(f"\nトークン「今日」(0番目) の仮の単語埋め込み (最初の5次元): {word_embeddings[0, :5]}")
print(f"トークン「今日」(0番目) の位置符号化 (最初の5次元): {positional_encodings_for_sentence[0, :5]}")
print(f"トークン「今日」(0番目) の最終入力ベクトル (最初の5次元): {final_input_vectors[0, :5]}")

このPythonコードを実行すると、まずコンソールにいくつかのprint文の結果が表示され、その後、位置符号化行列をヒートマップとして可視化したグラフウィンドウがポップアップするはずです。もしGoogle ColabやJupyter Notebookのような環境で実行していれば、グラフはノートブック内に直接表示されますね。

では、実行結果がどうなるか、そしてそれが何を意味しているのか、一緒に見ていきましょう。コンソールに出力される主な内容は、こんな感じになると思います(ランダムな値を含む部分は実行ごとに変わります)。

生成された位置符号化行列の形状: (50, 128)

仮の単語埋め込み行列の形状: (7, 128)
この文に対応する位置符号化行列の形状: (7, 128)
最終的な入力ベクトルの形状 (単語埋め込み + 位置符号化): (7, 128)

トークン「今日」(0番目) の仮の単語埋め込み (最初の5次元): [0.78130735 0.04937283 0.54509977 0.59192753 0.2068484 ]
トークン「今日」(0番目) の位置符号化 (最初の5次元): [0.         1.         0.         1.         0.        ]
トークン「今日」(0番目) の最終入力ベクトル (最初の5次元): [0.78130735 1.04937283 0.54509977 1.59192753 0.2068484 ]

まず、「生成された位置符号化行列の形状: (50, 128)」と表示されているのは、max_sequence_length=50embedding_dimension=128 と設定したので、50行(50個のトークン位置に対応)×128列(各位置を128次元のベクトルで表現)の行列が正しく作られたことを示しています。これが、いわば「位置情報の辞書」のようなものです。

そして、グラフ(ヒートマップ)の方に目を向けてみましょう。Y軸が文中のトークンの位置(0から49まで)、X軸がベクトルの次元(0から127まで)を表していて、色の濃淡がそれぞれの位置符号の値を表現しています(通常、-1から1の間の値を取ります)。この図をじっくり眺めると、先ほど説明した「次元ごとに異なる周期の波」が視覚的に確認できるはずです。

図:位置符号化行列のヒートマップ(イメージ)。横軸が次元、縦軸がトークンの位置。色の変化が、次元によって周期の異なる波を描いているのが特徴です。特に、X軸の左側(次元が小さい)ほど波が細かく(高周波数)、右側(次元が大きい)ほど波が緩やか(低周波数)になっている様子が見て取れます。

具体的には、X軸の左の方(次元インデックスが小さい部分)では、Y軸方向に色が細かく変化しているのが見えると思います。これは短い波長のサイン波/コサイン波に対応していて、トークンの位置が少し変わるだけで値が敏感に反応することを示しています。逆に、X軸の右の方(次元インデックスが大きい部分)では、Y軸方向の色の変化が非常に緩やかになっているはずです。これは長い波長の波に対応し、大まかな位置情報を捉えていると考えられますね。

後半の日本語テキストの例では、実際に7つのトークンからなる文に対して、この位置符号化がどう適用されるかを示しています。「仮の単語埋め込み行列の形状: (7, 128)」は、7つのトークンそれぞれが128次元の「意味」ベクトルで表現されていることを意味します。そして、「この文に対応する位置符号化行列の形状: (7, 128)」では、先ほど作った50トークン分の位置符号化行列から、この文に必要な最初の7トークン分の位置ベクトルを取り出しています。最後に、「最終的な入力ベクトルの形状: (7, 128)」で、これら「意味」ベクトルと「位置」ベクトルが足し合わされて、Transformerモデルへの実際の入力となるベクトル列が完成したことを確認できます。

一番下のprint文では、最初のトークン「今日」(位置0)について、足し算の前後のベクトルの一部を見比べています。位置0のサイン成分は \( \sin(0/W_i) = 0 \)、コサイン成分は \( \cos(0/W_i) = 1 \) となるため、位置符号化ベクトルの偶数次元は0、奇数次元は1になっているのが見て取れますね(ただし、これは \(i=0\) の場合。\(i\) が変わると分母も変わるので、厳密には少し異なりますが、大まかな傾向として)。この「意味」と「位置」が融合したベクトルこそが、Transformerが文脈を理解するための出発点となるわけです。いやー、こうやって実際に手を動かして、数字やグラフで確認すると、数式だけ見ていた時よりもずっと親しみが湧いてきませんか?

5. 位置符号化ベクトルの特性:目で見て、さらに深く理解する

先ほどPythonコードで実際に位置符号化行列を計算し、ヒートマップで可視化してみましたね。あのカラフルな図、ただ眺めているだけでも面白いのですが、実はそこにはTransformerが「位置」という情報をうまく扱うための、いくつかの重要な仕掛けが隠されています。ここでは、あの図から読み取れる位置符号化ベクトルの「なるほど!」な特性を、もう少し掘り下げて見ていきましょう。

特性①:各位置のベクトルは、ちゃんとユニークになっている!

まず一番大切なのは、文中のそれぞれの位置に対して、異なる(ユニークな)位置符号ベクトルが割り当てられているということです。ヒートマップで言うと、Y軸(トークンの位置)の値が変われば、X軸方向の色のパターン(つまりベクトルの中身)もちゃんと変わっている、という点です。これは当たり前のようでいて、実は非常に重要です。もし違う位置なのに同じような位置符号ベクトルが割り当てられてしまったら、モデルはそれらの位置を区別できなくなってしまいますからね。

例えるなら、クラスの生徒一人ひとりに固有の出席番号を振るようなものです。出席番号が違えば違う生徒だと分かるように、位置符号ベクトルが異なれば、モデルも「あ、これは文頭の単語だな」「これは真ん中あたりの単語だな」と区別できるようになるわけです。各位置に、いわば固有の「位置シグネチャ」や「位置の指紋」のようなものが与えられている、と考えても良いかもしれません。

特性②:次元ごとに波の「速さ」が違う!~細かい位置から大まかな位置まで~

ヒートマップをもう一度よく見てみてください。X軸(ベクトルの次元)に沿って、色の変化の仕方が違って見えませんか?

  • X軸の左側(次元インデックスが小さい方):Y軸(位置)方向に色がめまぐるしく変わっていますよね。これは、波長が短く、周波数が高いサイン波・コサイン波に対応しています。これらの次元は、トークンの位置がほんの少し(例えば1つ)ずれただけでも、値が敏感に変化します。まさに「細かい位置の変化」を捉えるセンサーのような役割です。
  • X軸の右側(次元インデックスが大きい方):こちらでは、Y軸方向に色がゆったりと変化しています。これは、波長が長く、周波数が低いサイン波・コサイン波です。これらの次元は、トークンの位置が多少変わっても、値はそれほど大きくは変わりません。どちらかというと、「文全体の中での大まかな位置(前方、中央、後方など)」を捉えるのに役立っていると考えられます。

この「次元ごとに役割分担する」というアイデアが、なんともエレガントだと思いませんか?異なる周波数の波を組み合わせることで、まるで音のスペクトルのように、位置情報を豊かな表現で捉えようとしているわけです。これにより、モデルは単に「何番目」というだけでなく、もっと複雑な位置のニュアンスも学習できる可能性が出てきます。

特性③:もしかして「相対的な位置関係」も表現できている?

そして、ここが三角関数を使うことの、もう一つの隠れた(かもしれない)メリットです。実は、この方法で生成された位置符号化ベクトルは、トークン間の相対的な位置関係を表現するのに都合が良い性質を持っていると考えられています。

詳細な数学的な証明に踏み込むと少し難しくなってしまうのでここでは割愛しますが、三角関数の「加法定理」というものを覚えていますか?例えば、\( \sin(\alpha+\beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta \) といったアレです。この加法定理を使うと、ある位置 \( pos \) の位置符号化ベクトル \( PE_{pos} \) と、そこから一定のオフセット \( k \) だけ離れた位置 \( pos+k \) の位置符号化ベクトル \( PE_{pos+k} \) の間に、ある種の線形的な関係性(具体的には、\( PE_{pos} \) と \( PE_k \) を使った行列演算で \( PE_{pos+k} \) が表現できるような関係)があることが示唆されるんです。

これが何を意味するかというと、モデルは「現在の単語から見て、2つ先の単語」とか「3つ前の単語」といった、相対的な距離感を学習する手がかりを得られるかもしれない、ということです。絶対的な位置(何番目か)だけでなく、他の単語との「間合い」のようなものも、この位置符号化の中に織り込まれている可能性があるんですね。

この相対位置の表現能力は、特にSelf-Attention機構が文中の単語同士の関連性を計算する際に、非常に重要になってきます。例えば、「その患者は昨日、頭痛を訴えた。そして今日、発熱も認めた。」という文があったとします。この文で「昨日」と「今日」という時間的な関係性をモデルが理解するためには、単にそれぞれの絶対位置だけでなく、「今日」が「昨日」の1つ後である、という相対的な情報が鍵になりますよね。三角関数ベースの位置符号化は、そうした学習を助けるポテンシャルを秘めている、と言えるかもしれません。

どうでしょう?単なるサインとコサインの組み合わせに見えて、実はこんなにも奥深い特性が隠されているなんて、ちょっとワクワクしませんか?これらの特性があるからこそ、Transformerは複雑な文構造や単語間の依存関係を、あれほど巧みに捉えることができるのかもしれませんね。

6. 医療テキストで光る「位置情報」のチカラ:具体的な応用シーンを覗いてみよう

ここまで、Transformerがどのようにして単語の「順番」という情報を捉えているのか、その仕組み(位置符号化)について詳しく見てきましたね。さて、この「順番を理解できる」という能力、実は医療の現場で扱われる様々なテキストデータをAIが読み解く上で、ものすごく大きな意味を持ってくるんです。考えてみれば、カルテにしろ論文にしろ、書かれていることの順番が入れ替わったら、意味が全く変わってしまうケースって、医療の世界にはゴロゴロしていますよね。位置符号化のおかげでTransformerがこの「順序」というパズルのピースを扱えるようになることは、医療AIの可能性をグンと広げてくれる、と言っても過言ではないでしょう。

では、具体的にどんな場面でこの「位置情報の理解」が役立つのか、いくつか典型的な応用例を挙げながら、その可能性を探ってみましょう。

応用例①:診療記録(カルテ)の「時間軸」を読み解く

電子カルテをはじめとする診療記録は、まさに時間の流れと共に記録されていく情報の宝庫です。患者さんの状態変化や治療の経過を正確に把握するためには、この時間軸に沿った情報の前後関係を理解することが不可欠ですよね。

  • 症状や検査結果の出現タイミングと順序:例えば、「3日前から微熱が続き、昨日からは咳が出始め、本日呼吸困難感が出現したため受診」といった記載があったとします。この「微熱 → 咳 → 呼吸困難感」という症状の出現順序は、診断を考える上で非常に重要な手がかりになります。位置符号化によって、Transformerはこれらのイベントがどの順番で起こったのかを認識し、それぞれの関連性を評価するのに役立ちます。「この症状の次には、あの症状が出やすい」といったパターンを学習できるかもしれません。
  • 治療介入と、その後の効果や副作用の経時変化:例えば、「降圧薬Aを10mgで投与開始。1週間後の診察で血圧は150/90mmHgから135/80mmHgに改善したが、めまいの訴えあり。」といった記録。ここでは、「薬剤A投与」というイベントと、「1週間後」という時間経過、そして「血圧改善」と「めまい出現」という結果が、時間軸上で結びついています。AIがこの前後関係を正確に捉えられれば、「この薬は、このくらいの期間で、これくらいの効果が出て、こういう副作用に注意が必要そうだ」といった知見をデータから自動で抽出できるかもしれません。副作用の早期発見や、治療効果の予測にも繋がる可能性がありますね。
応用例②:薬剤の投与順序が鍵となる治療シーケンスの分析

特にがん治療などでは、複数の薬剤を特定の順番で、特定の期間を空けて投与する「レジメン(治療計画)」が細かく定められています。この投与順序は、治療効果を最大限に引き出し、副作用を最小限に抑えるために非常に重要です。もしAIが過去の多数の治療記録から、薬剤の投与シーケンスと治療成績(例:生存期間、再発率など)との関連性を学習できれば、より個別化された治療計画の立案支援や、新しい治療法の開発に貢献できるかもしれません。位置符号化は、まさにこのような「手順」や「順番」が重要な意味を持つデータを扱う上で、Transformerの強力な武器となります。

応用例③:手術記録や看護記録にみる「手順」と「イベント」の流れの把握

手術の記録には、「皮膚切開 → 筋層剥離 → 腫瘍辺縁確認 → 腫瘍全摘出 → 止血確認 → ドレーン留置 → 閉創」といった一連の手順が詳細に記述されます。また、看護記録に目を向ければ、バイタルサインの測定、薬剤の投与、実施されたケア、患者さんの状態の変化(例えば、痛みの訴え、意識レベルの変化など)が、時系列に沿って記録されていますよね。これらの記録から、特定のイベント(例えば、術中の予期せぬ出血)が起きた際に、その前後にどのような操作や状態変化があったのか、といったパターンをAIが抽出できれば、医療安全の向上や、業務プロセスの改善に繋がるヒントが見つかるかもしれません。ここでも、イベントの「順序」を正しく認識できることが、AIの分析能力を左右します。

応用例④:医学研究論文の「実験プロトコル」を正確に再現・比較する

新しい治療法や診断技術が開発される過程では、多くの実験研究が行われます。その成果を記した医学論文には、実験の手順(プロトコル)が詳細に記載されているはずです。このプロトコルの「順序」は、実験の再現性や、異なる研究結果を比較検討する上で、極めて重要です。もしTransformerベースのAIが、複数の論文から実験プロトコルに関する記述を正確に抽出し、その手順の類似性や相違点を自動で比較できたらどうでしょう?これは、新しい研究を計画する際の参考にしたり、システマティックレビュー(多数の研究結果を統合して分析する手法)を効率化したりする上で、大きな助けになる可能性があります。論文という膨大なテキストの海から、重要な「手順」という情報を、その順序も含めて的確に抜き出す能力が求められるわけです。

少し想像を膨らませてみましょう。例えば、ある患者さんの電子カルテの記録をAIが時系列に沿って分析した結果、「この患者さんは、特定の症状(例:繰り返す頭痛)を訴える数ヶ月前から、睡眠導入剤の処方量が増加傾向にあり、かつ職場環境の変化に関する記述(例:部署異動、残業時間の増加)が記録されていることが多い」といった、人間では見逃しがちな微妙なパターンを発見できるかもしれません。これは、疾患のより早期の兆候を捉えたり、潜在的なリスク因子を特定したりする上で、非常に価値のある情報になり得ますよね。このような分析が可能になるのも、AIがテキスト中のイベントの「順序」や「時間的な隔たり」を、位置符号化という仕組みを通じて理解できるからこそ、と言えるでしょう。

もちろん、AIが全てを解決してくれるわけではありませんし、医療現場での応用には慎重な検証が不可欠です。それでも、Transformerが持つ「順序を理解する能力」は、これまで人間が膨大な時間をかけて行ってきた情報整理やパターン発見の作業を、大きく効率化し、新たな視点を与えてくれる可能性を秘めていると感じています。

7. 「位置」の表現、実は他にも色々あるんです:発展的なアプローチを少しだけご紹介

ここまで、Transformerの「伝家の宝刀」とも言える三角関数を使った位置符号化について、じっくりと見てきました。この方法は、そのシンプルさと賢さで、多くの研究者や開発者に影響を与えてきた、まさに古典的なテクニックです。でも、技術の世界は日進月歩。もちろん、この位置符号化についても、「もっと良い方法はないかな?」と、たくさんの研究者たちが知恵を絞って、様々な改良案や新しいアプローチを提案してきているんです。ここでは、そんな発展的な話題に少しだけ足を踏み入れて、代表的なものをいくつか簡単にご紹介したいと思います。深入りはしませんが、「こんな考え方もあるんだな」と知っておくだけでも、今後のAI技術のニュースなどに触れたときの理解度がグッと上がるかもしれませんよ。

アプローチ①:データから「位置の意味」を学習しちゃう!「学習可能な位置埋め込み」

まずご紹介したいのが、「学習可能な位置埋め込み(Learnable Positional Embeddings)」という方法です。名前の通り、位置情報を表すベクトルを、なんとモデル自身がデータから学習してしまう、というアプローチなんですね。BERT[2]やGPTシリーズ[4]といった、皆さんも一度は耳にしたことがあるかもしれない有名なTransformerベースのモデルたちも、実はこの方式を採用しています。

この方法の考え方は、単語の意味を学習する「単語埋め込み」と非常によく似ています。具体的には、まず処理したい文の最大の長さ(例えば512トークンとか)を決めておき、その長さ分の「位置ID」(0番目、1番目、…、511番目)を用意します。そして、それぞれの位置IDに対して、専用の埋め込みベクトルを割り当てるんです。このベクトルは、最初はランダムな値で初期化しておき、モデルが大量のテキストデータを学習する過程で、他のパラメータ(重み)と一緒に、徐々に「良い感じ」の値に調整されていきます。つまり、モデルが「このタスクでは、文頭に近い位置はこういうベクトルで表現するのが良さそうだ」「文末はこういう特徴を持たせよう」といったことを、データを通じて勝手に学んでくれるわけです。

このアプローチのメリットは、何と言っても特定のタスクやデータセットの特性に合わせて、最適な位置表現を柔軟に獲得できる可能性がある点でしょう。三角関数のような固定のルールに縛られず、データが語る「位置の意味」をダイレクトに学習できるのは魅力的です。私たちが新しい言語を学ぶとき、文法規則(固定ルール)だけでなく、たくさんの例文(データ)に触れることで、言葉のニュアンスや使われ方を体得していくのに似ているかもしれませんね。

ただ、良いことばかりではありません。この方法には、一つ大きな課題があります。それは、学習時に想定した最大の文長を超えるような、ものすごく長い未知の文に対して、うまく対応できない(汎化性能が低い)可能性がある、という点です。学習データに512トークンまでの文しか含まれていなかった場合、それより長い文の位置情報をどう表現すれば良いのか、モデルは知らないわけですからね。この点は、理論上は無限の長さに対応できる三角関数ベースの方法とは対照的です。また、学習すべきパラメータが増えるので、その分モデルサイズが少し大きくなったり、学習に必要なデータ量が増えたりする、という側面もあります。

【学習可能な位置埋め込みのイメージ】

                                    学習データ (大量のテキスト)
                                          │
                                          ▼
+-----------------+     +-------------------------------------+     +-----------------+
| トークン列      | --> | 単語埋め込み層 (意味をベクトル化)   | --> | 単語ベクトル列  |
| (例: "今日", "天気") |     (重みW_eを学習)                 |     | (例: V_今日, V_天気)|
+-----------------+     +-------------------------------------+     +-----------------+
                                          │                               │
                                          ▼                               │ 加算
+-----------------+     +-------------------------------------+     +-----------------+
| 位置ID列        | --> | 位置埋め込み層 (位置をベクトル化)   | --> | 位置ベクトル列  |
| (例: 0, 1)      |     (重みW_pを学習)                 |     | (例: P_0, P_1)  |
+-----------------+     +-------------------------------------+     +-----------------+
                                                                          │
                                                                          ▼
                                                                +-----------------+
                                                                | 最終的な入力    |
                                                                | (V_今日+P_0,    |
                                                                |  V_天気+P_1)   |
                                                                +-----------------+

● 単語埋め込み層: 各単語IDに対応するベクトル (意味) を学習。
● 位置埋め込み層: 各位置IDに対応するベクトル (順序) を学習。
● この2つのベクトルを加算して、Transformerの入力とする。
● W_e と W_p は、モデルの訓練中に誤差逆伝播によって更新される。

上の図は、学習可能な位置埋め込みの処理の流れをざっくりと示したものです。単語そのものの意味を表すベクトル(単語埋め込み)と、その単語の位置を表すベクトル(位置埋め込み)が、それぞれ独立した「埋め込み層」という部分で学習され、最後に足し合わされてTransformer本体への入力となる、というイメージですね。

アプローチ②:「絶対位置」より「相対位置」!「相対位置エンコーディング」

もう一つ、注目されているのが「相対位置エンコーディング(Relative Positional Encodings)」という考え方です。これは、Transformer-XL[5]というモデルで提案されて以来、様々な改良が加えられているアプローチです。この方法の面白いところは、「この単語は文頭から何番目」といった絶対的な位置に注目するのではなく、「ある単語と別の単語が、どれだけ離れているか」という相対的な距離を重視する点にあります。

例えば、「Aという単語から見て、Bという単語は2つ右隣にある」とか、「Cという単語は1つ左隣にある」といった情報ですね。Self-Attention機構が文中の単語同士の関連性を計算する際に、この「相対的な距離」の情報をうまく組み込んであげることで、より柔軟な文脈理解を目指そう、というわけです。特に、非常に長い文章を扱う場合、絶対的な位置だけでは情報が遠すぎて捉えきれないようなケースでも、相対的な近さ・遠さという観点なら意味を見出せるかもしれません。

この相対位置エンコーディングには、いくつかの具体的な実装方法がありますが、共通しているのは、Self-Attentionのスコア計算(どの単語にどれだけ注目するかを決める部分)に、単語間の相対距離に応じたバイアス項や、相対距離を考慮した特別な埋め込みベクトルを導入する、という点です。これにより、モデルは「近くにある単語同士は、より強く影響し合う傾向がある」といった直感的な関係性を、より自然に学習できると期待されています。また、学習可能な位置埋め込みが苦手としていた「学習データよりも長い文への対応」についても、改善が見られると報告されています。

医療の文脈で考えてみると、例えば患者さんの長い経過記録を読むときに、「直前の検査結果との比較」や「数日前の症状との関連」といった、時間的な「近さ」が重要な意味を持つことがありますよね。相対位置エンコーディングは、そういった局所的な依存関係を捉えるのに長けているかもしれません。

もちろん、これらの発展的な手法も万能というわけではなく、それぞれに得意なこと、苦手なことがあります。計算コストが増えたり、実装が複雑になったりすることもあります。それでも、研究者たちは常に「もっと賢く、もっと効率的に」と、新しいアイデアを生み出し続けているんですね。

ただ、どんなに新しい手法が登場しても、今回私たちがじっくり学んだ三角関数ベースの位置符号化は、そのシンプルさと根底にある考え方の美しさから、依然としてTransformerの基本を理解する上で非常に重要な技術であり続けると思います。いわば、全ての始まりの「型」のようなものかもしれませんね。この基本をしっかり押さえておくことが、きっと新しい技術を理解する上でも、大きな力になってくれるはずです。

8. まとめ:位置符号化が拓く、文脈理解の新たな地平

本セクションでは、Transformerにおける「位置符号化」の重要性、三角関数を用いた具体的な実装方法、その特性、そして医療分野での応用可能性について深掘りしました。位置符号化は、単語の「意味」だけでなく「順序」という情報をTransformerモデルに与えることで、より人間らしい、文脈に即した言語理解を可能にするための鍵となる技術です。

医療記録のように時系列情報が豊富なテキストデータを扱う上で、この位置符号化の仕組みを理解することは、生成AIの能力と限界を把握し、それを自身の研究や臨床にどのように活かせるかを考える上で、非常に大きな助けとなるでしょう。次にTransformerの他の構成要素を学ぶ際にも、この「入力に位置情報が加えられている」という前提を頭の片隅に置いておくと、より深い理解に繋がるはずです。

参考文献

  1. Vaswani A, Shazeer N, Parmar N, Uszkoreit J, Jones L, Gomez AN, et al. Attention is all you need. In: Advances in Neural Information Processing Systems 30 (NIPS 2017). Curran Associates, Inc.; 2017. p. 5998-6008.
  2. Devlin J, Chang MW, Lee K, Toutanova K. BERT: Pre-training of deep bidirectional transformers for language understanding. In: Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long and Short Papers). Minneapolis, Minnesota: Association for Computational Linguistics; 2019. p. 4171-4186.
  3. Lee J, Yoon W, Kim S, Kim D, Kim S, So CH, et al. BioBERT: a pre-trained biomedical language representation model for biomedical text mining. Bioinformatics. 2020 Feb 15;36(4):1234-1240.
  4. Radford A, Narasimhan K, Salimans T, Sutskever I. Improving language understanding by generative pre-training. OpenAI; 2018. Available from: https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf
  5. Dai Z, Yang Z, Yang Y, Carbonell J, Le QV, Salakhutdinov R. Transformer-XL: Attentive language models beyond a fixed-length context. In: Proceedings of the 57th Annual Meeting of the Association for Computational Linguistics. Florence, Italy: Association for Computational Linguistics; 2019. p. 2978-2988.

さて、ここまででTransformerに入力を渡す準備が整いました。次はいよいよ、その入力が内部でどのように処理されていくのか——まずはTransformerの心臓部とも言える「Self-Attention」機構を、数式と図を交えながら紐解いていきましょう。

Self-Attention:どの単語に注目しているのか?

Transformerの中核をなす仕組みが、Self-Attention(自己注意機構)です。この仕組みがなぜ重要かというと、「文の中で、ある単語が他のどの単語と関係しているのか」を自動的に学習し、柔軟に対応できるからです。

たとえば、「糖尿病の治療法は?」という文の中で、「治療法」という単語が「糖尿病」とどう関係しているかを、AIがその都度判断できるようになるのです。

Self-Attentionの基本的な考え方

自己注意では、各単語が他のすべての単語を見渡し、「どれにどれだけ注目すべきか(Attention)」を計算します。その計算の中核になるのが、以下の3つのベクトルです:

記号名前役割
QQuery(照会)「私は何を知りたいか?」という問い
KKey(鍵)「他の単語はどんな特徴を持っているか?」
VValue(値)実際に受け取る情報の中身

これらは、もとの入力ベクトル \( X \) から、重み行列(学習可能なパラメータ)を使って計算されます:

\( Q = XW^Q,\quad K = XW^K,\quad V = XW^V \)

この変換によって、同じ単語でも「尋ねる視点(Q)」と「答える特徴(K)」を分けて扱うことができます。

Attentionスコアの計算方法

では、実際にどれくらい注目すべきか(スコア)はどうやって計算するのでしょうか?以下の数式がSelf-Attentionの心臓部です:

\[ \text{Attention}(Q, K, V) = \text{softmax}\left( \frac{QK^\top}{\sqrt{d_k}} \right)V \]

この式の意味を、少し丁寧に分解して説明してみましょう:

  • \( QK^\top \):各Queryが、すべてのKeyとどれくらい一致するか(内積)を計算します。つまり、「自分が知りたいこと」と「他の単語が持っている特徴」の相性を数値化しているのです。
  • \( \sqrt{d_k} \):内積の値が大きくなりすぎるのを防ぐためのスケーリング係数。埋め込み次元の平方根で割って、数値が暴れないように調整します。
  • softmax:スコアを確率のように正規化します。「どの単語にどれだけ注目するか」を重み(和が1になる)として表現します。
  • × V:最後に、その重みに従ってValue(情報の中身)を合成します。

つまり、各単語は、他のすべての単語を見渡しながら、「誰から、どれだけ情報をもらうか」を動的に決めているわけです。

図でイメージするSelf-Attention

このようにして得られた出力ベクトルは、単語そのものの情報だけでなく、その周囲との意味的な関係性も含んだ「文脈化された表現」となります。

医学テキストでの具体例

たとえば、次のような文章があったとしましょう。

「インスリンは糖尿病患者に対して用いられる。」

このとき、「用いられる」という動詞は、「インスリン」にも「糖尿病患者」にもかかっています。Self-Attentionは、このような依存関係をスコアによって自動で学習し、モデルに「何が何に使われているのか」という構造的な理解を与えるのです。

この仕組みがあるからこそ、Transformerは非常に長い文章や専門的な内容でも、柔軟に処理できるわけですね。

Multi-Head Attention:多角的な視点での理解

前章で取り上げたSelf-Attentionは、文中のすべての単語同士の関係性を動的に計算する強力な仕組みでした。しかし、実はTransformerはこの「Attention」を1回だけ行って終わり、というわけではありません。むしろ、その「見方」を複数同時に走らせて、異なる側面から文脈を理解しようとします。

この仕組みが、Multi-Head Attention(多頭注意機構)です。名前のとおり、複数の“ヘッド(視点)”を同時に使って、それぞれが異なる意味の捉え方を学習していきます。

なぜ「複数の視点」が必要なのか?

人間の読解にも通じる話ですが、ひとつの文章を読むとき、私たちは単に単語を1回ずつ見ているわけではありません。主語と述語の関係を見たり、因果関係をたどったり、前後の言い換え表現を見抜いたりと、複数の意味的ルートを無意識に並行して追っています。

Multi-Head Attentionもそれとよく似ています。各ヘッドが、文中の異なる関係性やパターンに敏感になることで、全体として豊かで多層的な理解を形成するのです。

たとえば:

  • あるヘッドは「主語と述語の対応」を学習
  • 別のヘッドは「医療用語同士の共起関係(例:糖尿病 ↔ インスリン)」を捉える
  • さらに別のヘッドは「否定や条件のニュアンス」に着目しているかもしれません
イメージ

Multi-Head Attentionの数式と構造

各ヘッドでは、それぞれ独立した重み行列を使って \( Q, K, V \) を計算します。それをまとめて並列的に処理し、最終的に統合します。

\[ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W^O \]

ここで、各ヘッドは次のように定義されます:

\[ \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) \]

  • \( W_i^Q, W_i^K, W_i^V \):それぞれのヘッドに固有の重み行列
  • \( W^O \):統合後に出力へ変換する重み行列

この構造により、Self-Attentionの計算がh個の異なる視点で並列に行われ、それらが一つにまとめられて次の層へ渡される仕組みになっています。

構造イメージ(図解)

このように、1つ1つのヘッドはシンプルなSelf-Attentionですが、それを“h個”同時に走らせて多面的な情報を引き出すことで、Transformerは非常に豊かな表現力を持つようになります。

医療テキストにおける実例

たとえば以下のような医療文書があったとしましょう。

「本剤は高血圧患者に禁忌であるが、糖尿病には有効とされる。」

この一文には、否定と条件、複数の疾患名と薬剤の対応関係が含まれています。おそらく、あるAttentionヘッドは「高血圧 ↔ 禁忌」に強く反応し、別のヘッドは「糖尿病 ↔ 有効」の関係性を抽出するはずです。

こうした「視点の分担」は、Transformerが複雑な医学的記述を読み解ける理由のひとつだと言えるでしょう。

Feed Forward Network(FFN):意味の深化

Self-AttentionやMulti-Head Attentionによって、Transformerは「どの単語がどの単語に注目すべきか」という関係性を文脈から学習しました。しかし、文の意味を深く理解し、より抽象的な表現を獲得するためには、もう一歩踏み込んだ処理が必要になります。

そこで登場するのが、各層の後半に配置されたFeed Forward Network(FFN)です。これは、各トークンに対して独立に適用される小さなニューラルネットワークで、主に「意味の抽象化」や「表現の強化」を担っています。

ちょっと感覚的な表現になりますが、「Attentionで集めた文脈の材料を、自分なりに解釈し直して、より洗練された意味ベクトルに磨き上げる」というイメージが近いかもしれません。

構造と数式

FFNは、非常にシンプルな2層の全結合(線形)ニューラルネットワークです。その間に活性化関数(通常はReLU)を挟みます。

具体的な数式は以下のようになります:

\[ \text{FFN}(x) = \text{ReLU}(xW_1 + b_1)W_2 + b_2 \]

  • \( x \):Attentionの出力(トークンの文脈ベクトル)
  • \( W_1, W_2 \):線形変換の重み行列
  • \( b_1, b_2 \):それぞれのバイアス項
  • ReLU:非線形性を加える活性化関数(負の値を0にカットオフする)

この計算によって、Attentionで得た文脈情報を「より抽象的な次元」へ変換できるようになります。なお、この処理は各トークンに対して独立に実行されるため、文全体の情報ではなく「単語ごとの意味深化」に特化しています。

図で理解するFFNの流れ

【入力ベクトル(各トークン)】
          ↓
   線形変換(W₁ + b₁)
          ↓
   非線形処理(ReLU)
          ↓
   線形変換(W₂ + b₂)
          ↓
【抽象化された出力ベクトル】

このように、直線的な変換と非線形な活性化を組み合わせることで、モデルはより複雑な意味のパターンを表現できるようになります。医学的な文脈で言えば、単なる「インスリン」という単語が、「糖尿病治療薬の一種」という抽象概念へと変換されるようなイメージです。

医学テキストにおける応用のヒント

医療記録や論文の処理において、Attentionは文脈のつながりを捉えるのに役立ちますが、FFNはその情報を「意味的に強化」する働きを担っています。

たとえば、カルテに書かれた「HbA1cが8.4%」という数値と、「インスリン開始」の記載を同時に見たとき、モデルはそれらが「高血糖 → 治療強化」という医学的因果の流れにあると理解しなければなりません。FFNはこうした複雑な意味づけを支える要素の一つだと考えられています。

残差接続とLayer Normalization:深くても安定に

Transformerのように深い層を何重にも重ねるモデルでは、訓練時に「勾配が消えてしまう(Vanishing Gradient)」や「学習が途中で発散する」といった問題が起きやすくなります。これは、人間に例えるなら、何十回も伝言ゲームを繰り返すうちに、最初の内容が薄まってしまうようなイメージです。

これを防ぐために、Transformerでは2つのテクニックが導入されています:

  • 残差接続(Residual Connection)
  • Layer Normalization(層正規化)

この2つが協調することで、非常に深いネットワークであっても、学習がスムーズに行われるように設計されています。

残差接続とは?

まずは残差接続(Residual Connection)から見てみましょう。これは、「変換結果に、変換前の入力を足し戻す」という非常にシンプルなアイデアです。

たとえば、Self-AttentionやFFNのようなサブレイヤーの出力をそのまま使うのではなく、そこに元の入力を加えるのです。こうすることで、「もともとの意味を保ちつつ、変化を加える」という構造になります。

数式で表すと、次のようになります:

\[ \text{Output} = \text{LayerNorm}(x + \text{SubLayer}(x)) \]

  • \( x \):サブレイヤーに入る元の入力
  • \( \text{SubLayer}(x) \):Attention や FFNなどの変換結果
  • \( + \):入力をそのまま「足し戻す」操作(ショートカット接続)
  • \( \text{LayerNorm} \):その後に正規化を適用

これは、深層学習における有名な「ResNet」でも使われている技法で、勾配が途中で失われずに流れやすくなるという効果があります。

Layer Normalization(正規化)とは?

次に、Layer Normalizationです。これは、「ベクトルの各要素がばらばらなスケールや分布を持っていると学習が不安定になる」ことに対処するための技術です。

Transformerでは、各トークンごとに、埋め込みベクトル(たとえば768次元)の平均と分散をとり、下記のように正規化します:

\[ \text{LayerNorm}(x) = \frac{x – \mu}{\sqrt{\sigma^2 + \epsilon}} \cdot \gamma + \beta \]

  • \( \mu \):ベクトルの平均
  • \( \sigma^2 \):分散
  • \( \epsilon \):ゼロ割り防止の小さな定数
  • \( \gamma, \beta \):学習可能なスケーリング・シフトパラメータ

この操作によって、ベクトルのスケールが整い、勾配の流れが安定しやすくなります。つまり、ニューラルネットが変な方向に飛び出さず、スムーズに収束していくための「地ならし」のようなものです。

図で理解する流れ

【入力ベクトル(x)】
          ↓
     サブレイヤー(Self-AttentionやFFN)
          ↓
【変換後ベクトル:SubLayer(x)】
          ↓
   入力xと足し算 → 残差接続(x + SubLayer(x))
          ↓
   Layer Normalization(層正規化)
          ↓
【次の層へ】

このように、Transformerの1層は、AttentionやFFNだけでなく、「足して正規化して渡す」という安定化の仕組みを内包しているのです。

医療AIにおける残差と正規化の意義

医療文書は長くて複雑な構造を持つことが多く、Transformerが深くなればなるほど情報の劣化やノイズの蓄積が懸念されます。こうした場面でも、残差接続とLayer Normalizationは非常に重要な役割を果たします。

モデルが長文のカルテや論文を正確に読み解くために、「元の情報を失わず、かつ意味を深めながら処理する」というのは大前提です。その支えとなっているのが、こうした地味ながら強力なメカニズムなのです。

医療応用への示唆

ここまで見てきたように、Transformerは「どの語に注目すべきか(Attention)」「意味を抽象化する(FFN)」「深い層でも安定して学習できる(Residual + LayerNorm)」といった数々の強力な仕組みを備えています。

では、こうした構造は、私たちが日々取り組む医療の実務や研究にどう活かせるのでしょうか?実は、Transformerの応用先は単なる“文章の理解”にとどまらず、診療支援・研究・患者対応など、多方面に広がっています。

以下に代表的な応用領域と、それぞれの活用方法を整理してみましょう。

応用領域具体的な機能期待される効果
診療録の文脈理解「症状」「薬剤」「時系列」などを文脈的に結びつける疾患の経時的変化や薬剤の効果を自動抽出
医療対話生成患者の発話の意図を多角的に理解し、自然な応答を生成診療効率の向上、患者満足度の向上
研究文献の自動要約複数段落を読み取り、要点を抽出・再構成短時間で大量の論文内容を把握可能に
医学概念の抽象化と分類疾患・治療法・検査の関係性を言語的にマッピング知識ベース構築や臨床推論支援への応用

実感を込めて言えば…

私自身、医療文書や対話の中で「この発言はどこにつながっているのか」「この所見は診断や治療にどう関係しているのか」といった関係性を追いかけることに多くの時間を使ってきました。

Transformerのようなモデルが、その意味的な「つながり」を自動的に抽出し、示唆を与えてくれるとしたら——それは医師にとって、まさにセカンドブレインのような存在になりうると思います。

もちろん、万能ではありませんし、現場での信頼性や安全性は慎重に検証されるべきです。ただ、ここでご紹介してきた構造の「意図」と「強み」を理解することは、医療の未来に向けた第一歩としてとても大切だと感じています。

まとめ:Transformerの1層に込められた力

ここまで解説してきたように、Transformerの「1層」の中には、驚くほど緻密で洗練された構造が詰め込まれています。

ぱっと見た印象とは裏腹に、それぞれの構成要素は、互いに独立して働いているわけではなく、まるでチームのように補完し合っています。以下にその構造の流れをもう一度整理してみましょう:

【Transformer 1層の流れ】

① Self-Attention:文の中で「どこに注目すべきか」を学習
        ↓
② Multi-Head Attention:多視点で意味のつながりを捉える
        ↓
③ FFN(Feed Forward Network):得られた文脈を抽象化・強化
        ↓
④ 残差接続 + Layer Normalization:
   深い構造でも安定して学習できるよう調整

この一連の処理が、各トークン(単語)ごとに繰り返し行われていることを考えると、Transformerがどれほど「意味」を丁寧に扱おうとしているか、少しずつ実感できてきたのではないでしょうか。

そして、この1層が12層、24層…と重なっていくことで、モデルは非常に高次な意味理解を実現します。たとえば、文脈に応じた「糖尿病」という言葉の意味の揺れ(病名?診療対象?生活指導?)を正確に読み取り、適切な処理につなげることが可能になるわけです。

最後に

Transformerは、構造として見ればシンプルな部品の組み合わせです。しかし、その組み方と流れには、人間の「言葉の理解」の本質を突こうとする深い思想が見え隠れしています。

今回見てきたのは、ほんの“1層”の世界。でもこの1層の仕組みを理解しておくことは、生成系AIの本質に触れ、自分の研究や現場応用に活かしていくうえで、大きな意味があるはずです。

次回以降は、これらの層が「どう積み重なり」「どう出力を生むか」、そして「どうファインチューニングされて医療に適応されていくか」について掘り下げていきます。

引き続き、一緒に探究していきましょう。

Deep Dive!:Transformerの計算フロー・主要テンソル・パラメータ 一覧

Deep Dive!:Transformerの計算フロー・主要テンソル・パラメータ 一覧

Transformerの計算フロー:主要数式一覧

処理段階数式意味・目的
語彙埋め込み\( \mathbf{X} = \text{Embed}(\text{tokens}) \)入力トークンをベクトルに変換
位置埋め込みの加算\( \mathbf{H}^{(0)} = \mathbf{X} + \mathbf{P} \)単語の順序情報を加える
Queryの計算\( \mathbf{Q} = \mathbf{H}^{(l)} \mathbf{W}_Q \)入力を「問い」に変換
Keyの計算\( \mathbf{K} = \mathbf{H}^{(l)} \mathbf{W}_K \)入力を「答えの鍵」に変換
Valueの計算\( \mathbf{V} = \mathbf{H}^{(l)} \mathbf{W}_V \)入力を「内容」に変換
Attentionスコア\( \mathbf{S} = \frac{ \mathbf{Q} \mathbf{K}^\top }{ \sqrt{d_k} } \)関連度を計算(内積+スケーリング)
Attention重み\( \mathbf{A} = \text{softmax}(\mathbf{S}) \)注目度を確率分布に変換
Self-Attention出力\( \mathbf{Z} = \mathbf{A} \mathbf{V} \)加重平均による文脈ベクトル生成
各ヘッドの出力\( \text{head}_i = \text{Attention}(\mathbf{QW}_Q^i, \mathbf{KW}_K^i, \mathbf{VW}_V^i) \)異なる視点での注意
Multi-Head結合\( \mathbf{M} = \text{concat}(\text{head}_1, \dots, \text{head}_h) \)全ヘッドの出力を統合
線形変換\( \mathbf{O} = \mathbf{M} \mathbf{W}_O \)次層用に整形
残差接続+正規化①\( \mathbf{H}’ = \text{LayerNorm}(\mathbf{H}^{(l)} + \mathbf{O}) \)Attentionの安定化
FFN第1層\( \mathbf{F}_1 = \text{ReLU}(\mathbf{H}’ \mathbf{W}_1 + \mathbf{b}_1) \)非線形変換(意味の抽象化)
FFN第2層\( \mathbf{F}_2 = \mathbf{F}_1 \mathbf{W}_2 + \mathbf{b}_2 \)元の次元に戻す
残差接続+正規化②\( \mathbf{H}^{(l+1)} = \text{LayerNorm}(\mathbf{H}’ + \mathbf{F}_2) \)FFN後の出力を安定化
最終出力\( \mathbf{H}^{(L)} \)Transformer全体の出力(最終層)
語彙スコア(Logits)\( \text{logits}_t = \mathbf{h}_t \cdot \mathbf{E}^\top \)各語彙に対応するスコア
出力確率\( \mathbf{p}_t = \text{softmax}(\text{logits}_t) \)次の語の出現確率

Transformerの計算フローを具体的な数値で追ってみよう(小型モデルで図解用)

以下では、Transformerの内部処理をすべて数値付きで辿れるよう、小さなモデル設定に基づいて具体的に説明します。

モデル設定(例)

項目
語彙数 \( V \)8語
文長 \( T \)5トークン(例:「糖 尿 病 の 治療」)
埋め込み次元 \( d \)4
ヘッド数 \( h \)2
各ヘッド次元 \( d_k = d/h \)2
FFN中間層次元 \( d_{ff} \)6

① Embeddingと位置符号化:入力文をベクトルで表すまで

Transformerでは、文字列(単語やサブワード)をそのまま扱うことはできません。まずは「トークンID」へ変換した後、それぞれを意味のある数値ベクトルへ変換する必要があります。

ステップ1:トークンIDへの変換

たとえば、以下のような5語からなる日本語の短い入力文があるとします:

入力文:糖 尿 病 の 治療
トークン列:[1, 2, 3, 4, 5]

ステップ2:語彙埋め込み行列からベクトルへ変換

Transformerは、各トークンIDを多次元ベクトルに変換するために、語彙埋め込み行列 \( \mathbf{E} \in \mathbb{R}^{8 \times 4} \) を持っています。

この行列は、語彙数(ここでは8語)に対して、それぞれの単語に対応する4次元の意味ベクトルを保持しています。

たとえば、以下は5つのトークンに対応するベクトルを取り出して並べた埋め込みベクトル列 \( \mathbf{X} \in \mathbb{R}^{5 \times 4} \) の例です:

\[
\mathbf{X} =
\begin{bmatrix}
0.1 & 0.3 & -0.1 & 0.2 \\
-0.2 & 0.0 & 0.5 & 0.1 \\
0.3 & 0.1 & -0.3 & 0.4 \\
0.0 & -0.1 & 0.2 & 0.2 \\
0.1 & 0.4 & 0.1 & -0.1 \\
\end{bmatrix}
\]

この段階で、入力された文は「5個のトークン × 各トークン4次元のベクトル」という形で数値化されました。

ステップ3:位置符号(Positional Encoding)の加算

Transformerはリカレント(時系列)構造を持たないため、トークンの「順序情報」を別途与える必要があります。

そのため、位置埋め込み行列 \( \mathbf{P} \in \mathbb{R}^{5 \times 4} \) を使い、各トークン位置(1〜5)に対応する位置ベクトルを以下のように加算します:

\[
\mathbf{H}^{(0)} = \mathbf{X} + \mathbf{P}
\]

たとえば、以下のような位置埋め込みがあったとします:

\[
\mathbf{P} =
\begin{bmatrix}
0.0 & 0.1 & 0.0 & 0.1 \\
0.1 & 0.0 & 0.1 & 0.0 \\
0.2 & 0.1 & 0.0 & 0.1 \\
0.3 & 0.0 & 0.1 & 0.0 \\
0.4 & 0.1 & 0.0 & 0.1 \\
\end{bmatrix}
\]

これを加算した結果:

\[
\mathbf{H}^{(0)} =
\begin{bmatrix}
0.1 & 0.4 & -0.1 & 0.3 \\
-0.1 & 0.0 & 0.6 & 0.1 \\
0.5 & 0.2 & -0.3 & 0.5 \\
0.3 & -0.1 & 0.3 & 0.2 \\
0.5 & 0.5 & 0.1 & 0.0 \\
\end{bmatrix}
\]

このようにして、各単語はその「意味(埋め込み)」と「文中の位置(位置符号)」の両方を反映したベクトルとして初期入力が完成します。

まとめ:ここまでの処理

  • 文字列 → トークンIDに変換
  • トークンID → 埋め込み行列を使ってベクトルへ変換(意味の数値化)
  • 位置埋め込みを加算して順序情報を与える

この結果得られた \( \mathbf{H}^{(0)} \in \mathbb{R}^{5 \times 4} \) は、Transformerモデルの最初の層(Self-Attention)に入力される、文の数値的な「初期表現」です。

② Self-Attention(各ヘッド)

各ヘッドごとに、以下のように Query, Key, Value を計算します。

\[
\mathbf{Q}_i = \mathbf{H}^{(l)} \mathbf{W}_Q^i \quad \mathbf{K}_i = \mathbf{H}^{(l)} \mathbf{W}_K^i \quad \mathbf{V}_i = \mathbf{H}^{(l)} \mathbf{W}_V^i
\]

各行列のサイズは以下の通りです:

  • \( \mathbf{W}_Q^i, \mathbf{W}_K^i, \mathbf{W}_V^i \in \mathbb{R}^{4 \times 2} \)
  • \( \mathbf{Q}, \mathbf{K}, \mathbf{V} \in \mathbb{R}^{5 \times 2} \)

スコア行列:

\[
\mathbf{S} = \frac{\mathbf{Q} \mathbf{K}^\top}{\sqrt{2}} \in \mathbb{R}^{5 \times 5}
\]

Softmaxにより Attention重み:

\[
\mathbf{A} = \text{softmax}(\mathbf{S}) \in \mathbb{R}^{5 \times 5}
\]

文脈ベクトル出力:

\[
\mathbf{Z}_i = \mathbf{A} \mathbf{V}_i \in \mathbb{R}^{5 \times 2}
\]

③ Multi-Head Attention

すべてのヘッド出力を結合:

\[
\text{concat}(\mathbf{Z}_1, \mathbf{Z}_2) \in \mathbb{R}^{5 \times 4}
\]

線形変換で統合:

\[
\mathbf{O} = \text{concat} \cdot \mathbf{W}_O \quad \left( \mathbf{W}_O \in \mathbb{R}^{4 \times 4} \right)
\]

④ 残差接続とLayer Normalization(1回目)

\[
\mathbf{H}’ = \text{LayerNorm}(\mathbf{H}^{(l)} + \mathbf{O}) \in \mathbb{R}^{5 \times 4}
\]

⑤ Feed Forward Network(FFN)

重み行列:

  • \( \mathbf{W}_1 \in \mathbb{R}^{4 \times 6} \)
  • \( \mathbf{W}_2 \in \mathbb{R}^{6 \times 4} \)

処理:

\[
\mathbf{F}_1 = \text{ReLU}(\mathbf{H}’ \cdot \mathbf{W}_1 + \mathbf{b}_1) \in \mathbb{R}^{5 \times 6}
\]

\[
\mathbf{F}_2 = \mathbf{F}_1 \cdot \mathbf{W}_2 + \mathbf{b}_2 \in \mathbb{R}^{5 \times 4}
\]

⑥ 残差接続とLayer Normalization(2回目)

\[
\mathbf{H}^{(l+1)} = \text{LayerNorm}(\mathbf{H}’ + \mathbf{F}_2) \in \mathbb{R}^{5 \times 4}
\]

⑦ 出力生成(語彙スコアと確率)

Transformer最終出力:

\[
\mathbf{H}^{(L)} \in \mathbb{R}^{5 \times 4}
\]

語彙埋め込みの転置:

\[
\mathbf{E}^\top \in \mathbb{R}^{4 \times 8}
\]

各トークン位置 \( t \) におけるスコアと確率:

\[
\text{logits}_t = \mathbf{h}_t \cdot \mathbf{E}^\top \in \mathbb{R}^{8}
\]

\[
\mathbf{p}_t = \text{softmax}(\text{logits}_t) \in \mathbb{R}^{8}
\]

まとめ:小さな数値でTransformerを視覚化

このように、文長や次元数を小さく設定することで、Transformerの計算フローを図やイラストで直感的に可視化しやすくなります。図解を行う際には、各テンソルのサイズ・役割・変換先を対応させることが重要です。

Transformerモデルにおける主要テンソル・パラメータ 一覧

名称記号形状役割・意味
語彙埋め込み行列\( \mathbf{E} \)\( V \times d \)語彙IDを意味ベクトルに変換。Decoderでは出力時にも再利用(共有)。
トークン埋め込み列\( \mathbf{X} \)\( T \times d \)入力文の各トークンを埋め込んだベクトル列。
位置埋め込み行列\( \mathbf{P} \)\( T \times d \)単語の並び順をベクトル空間に埋め込む。
初期入力ベクトル\( \mathbf{H}^{(0)} = \mathbf{X} + \mathbf{P} \)\( T \times d \)Self-Attention層への初期入力。
各層の出力(隠れ状態)\( \mathbf{H}^{(l)} \)\( T \times d \)第 \( l \) 層の出力ベクトル(全層同形状)。
クエリ重み行列\( \mathbf{W}_Q \)\( d \times d_k \)入力をクエリ空間に変換。
キー重み行列\( \mathbf{W}_K \)\( d \times d_k \)入力をキー空間に変換。
バリュー重み行列\( \mathbf{W}_V \)\( d \times d_k \)入力をバリュー空間に変換。
スコア行列\( \mathbf{S} = \frac{\mathbf{Q} \mathbf{K}^\top}{\sqrt{d_k}} \)\( T \times T \)各トークン間の相関スコア。
Attention重み\( \mathbf{A} \)\( T \times T \)スコアのsoftmax正規化後。
Attention出力(単一ヘッド)\( \mathbf{Z} \)\( T \times d_k \)重み付きValueベクトルの合成結果。
マルチヘッド出力(結合)\( \text{concat}(head_1,\dots,head_h) \)\( T \times d \)全ヘッドを結合した出力。
多頭統合用線形変換\( \mathbf{W}_O \)\( d \times d \)マルチヘッドの出力を再統合。
FFNの1層目\( \mathbf{W}_1 \)\( d \times d_{ff} \)非線形変換用の中間層。
FFNの2層目\( \mathbf{W}_2 \)\( d_{ff} \times d \)元の次元に戻す線形層。
最終出力ベクトル\( \mathbf{H}^{(L)} \)\( T \times d \)語彙スコア計算に用いる最終表現。
語彙スコア(logits)\( \text{logits}_t = \mathbf{h}_t \cdot \mathbf{E}^\top \)\( V \)各語彙に対するスコア。
語彙の出現確率\( \mathbf{p}_t = \text{softmax}(\text{logits}_t) \)\( V \)次の語が出現する確率分布。

参考文献

  1. Vaswani A, et al. Attention is All You Need. Advances in Neural Information Processing Systems. 2017;30.
  2. Devlin J, et al. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. arXiv preprint arXiv:1810.04805. 2018.
  3. Lee J, et al. BioBERT: a pre-trained biomedical language representation model for biomedical text mining. Bioinformatics. 2020;36(4):1234–1240.
  4. Li I, et al. Fine-tuning pre-trained language models for biomedical question answering. Findings of EMNLP. 2021.
  5. Kiyono S, et al. Transformer-based Japanese Text-to-Text Pretraining for Multitask Learning. arXiv:2109.03628. 2021.
免責事項・注意事項等
  • 法令遵守について
    本教材はあくまで一般的な学習参考用の内容であり、医師法、薬機法、個人情報保護法、医療広告ガイドライン等の適用判断については、必ず厚生労働省・PMDA・経済産業省・各学会などの最新の法令・ガイドラインをご自身でご確認のうえご利用ください。
  • 医療行為の責任について
    本資料で紹介する AI 技術・手法は臨床診断・治療の補助を目的としていますが、最終的な診断・治療方針の決定および報告書の承認は必ず医師などの専門資格保持者が行ってください。AI の結果のみを鵜呑みにせず、Human-in-the-Loop の原則に則り、人間専門家による検証プロセスを実施してください。
    本コンテンツは、医療AIに関する技術的な学習と理解を深めることを目的としており、特定の医療行為を推奨、あるいは医学的助言を提供するものではありません。実際の臨床判断は、必ず担当の医療専門家にご相談ください。
  • プライバシー保護について
    本コース内で紹介するデータセットを利用する際は、各データセットのライセンス条件および研究倫理指針を厳守し、患者情報の匿名化・同意取得など必要な個人情報保護措置を確実に講じてください。
  • 知的財産権について
    本記事中の図表・コード・文章などを二次利用または転載する場合は、必ず引用元を明示し、権利者の許諾および適切なライセンス表記を行ってください。
  • 情報の正確性について
    本資料に記載する数値、事例、ライブラリのバージョン情報などは執筆時点の情報に基づいています。機能やライブラリはアップデートにより変更される可能性がありますので、必ず最新の公式ドキュメントや文献をご確認のうえ適宜アップデートしてご活用ください。
  • AI 活用の留意点
    本内容には AI の提案をもとに作成した部分が含まれていますが、最終的には専門家の監修・編集のもとで公開しています。特に生成系 AI を用いる場合は、ハルシネーション(誤情報)やバイアスに十分注意し、必ず人間専門家が結果を検証してください。
  • 免責事項
    本資料の利用によって生じたいかなる損害についても、著作者および提供者は一切の責任を負いません。研究・学習目的以外での利用はご遠慮ください。
  • 対象読者・前提知識
    本教材は医療従事者および AI 技術に関心のある技術者を主対象とし、Python の基礎知識や統計学の初歩的理解を前提としています。
  • 環境・互換性
    本資料で扱うコード/ライブラリは執筆時点の特定バージョンを前提としています(例:PyTorch 2.0、Transformers 4.x)。実行環境(OS、ハードウェア、依存パッケージ)によっては動作しない場合がありますのでご注意ください。
  • 免責範囲の明確化
    本教材に記載された内容はいかなる場合も「診療行為」としての効力を持ちません。製品やサービスの導入検討にあたっては、別途法務・品質保証部門との協議および正式な承認プロセスを経てください。
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人/About the Author

AI physician-scientist・連続起業家・元厚生労働省医系技官・医師・医学博士・ハーバード大学理学修士・ケンブリッジ大学MBA・コロンビア大学行政修士。
岡山大学医学部卒業後、内科・地域医療に従事。厚生労働省入省、医療情報技術推進室長、医療国際展開推進室長、救急・周産期医療等対策室長、災害医療対策室長等を歴任。文部科学省出向中はライフサイエンス、内閣府では食の安全、内閣官房では医療分野のサイバーセキュリティを担当。国際的には、JICA日タイ国際保健共同プロジェクトのチーフ、WHOインターンも経験。
退官後は、日本大手IT企業にて保健医療分野の新規事業開発や投資戦略に携わり、英国VCでも実務経験を積む。また、複数社起業し、医療DX・医療AI、デジタル医療機器開発等に取り組むほか、東京都港区に内科クリニックを開業し、社外取締役としても活動。
現在、大阪大学大学院医学系研究科招へい教授、岡山大学研究・イノベーション共創機構参事、ケンブリッジ大学ジャッジ・ビジネススクールAssociate、広島大学医学部客員教授として、学術・教育・研究に従事。あわせて、医療者のための医療AI教室「Medical AI Nexus」を主宰。
社会医学系指導医・専門医・The Royal Society of Medicine Fellow

コメント

コメントする

目次