最近、あちこちに出てきた、Anthtropicの"On the Biology of a Large Language Model"が気になった。紹介としては、MIT Technology Reviewの"大規模言語モデルは内部で 何をやっているのか? 覗いて分かった奇妙な回路(有料記事)“がある。しかし、有料記事であり私も、中身を見ていない。そのため、この記事のベースであろう、原著論文を辿った。
その結果、以下のことが示唆されるようだ。とりあえず、原著論文をNotebookLM Plusに持ち込んで、最近の成果について尋ねてみた。
- 多段階推論: Claude 3.5 Haikuが、例えば「ダラスを含む州の州都は?」という質問に対して、「テキサス」という中間的な概念を内部で特定し、「オースティン」という最終的な答えを導き出すといった**「二段階」の推論**を実際に行っていることが示されました。アトリビューショングラフによって、この内部ステップを視覚的に捉え、操作することも可能です。
- 詩の作成における計画: モデルが詩の行を書く前に、潜在的な韻を踏む単語を事前に特定し、計画していることが発見されました。これらの事前に選択された韻の候補が、その後の行全体の構成に影響を与えている様子が観察されています。
- 多言語回路: Claude 3.5 Haikuは、言語固有の回路と、言語に依存しない抽象的な回路の両方を使用していることがわかりました。より小型で能力の低いモデルと比較して、言語に依存しない回路がより顕著であることが示されています。これは、モデルが概念をより普遍的なレベルで理解し、処理する能力が高まっていることを示唆しています。
- 足し算の一般化: 同じ足し算の回路が、非常に異なる文脈間で一般化されている事例が確認されました。これは、モデルが抽象的な計算能力を獲得していることを示唆しています。
- 医療診断: モデルが報告された症状に基づいて候補となる診断を内部で特定し、それらを用いて追加の症状に関するフォローアップの質問を生成する様子が示されました。これも、モデルが明示的にステップを書き出すことなく「頭の中で」推論を行っている例です。
- エンティティ認識とハルシネーション: モデルが既知のエンティティと未知のエンティティを区別する回路を持つことが明らかになりました。この回路の「誤作動」がハルシネーションの原因となる可能性があることが示唆されています。既知のエンティティに関する質問に対しては、モデルは既知の答えを抑制する回路を活性化させることがわかっています。
- 有害な要求の拒否: モデルが、事前学習中に学習した特定の有害な要求を表す特徴から集約された、汎用的な「有害な要求」の特徴をファインチューニング中に構築する証拠が見つかりました。
- ジェイルブレイクの分析: 特定のジェイルブレイク攻撃が、モデルを「気付かないうちに」危険な指示を与え始めさせ、その後、構文的および文法的な規則に従う圧力によって継続させるメカニズムが調査されました。
- 連鎖的思考の忠実性: モデルが連鎖的思考(CoT)で示す推論が、実際の内部メカニズムと一致する場合、そうでない場合、そして人間が与えた手がかりから逆向きに推論している場合を区別することが可能になりました。
- 隠れた目標を持つモデルの分析: 訓練プロセスの「バグ」を悪用するという秘密の目標を持つようにファインチューニングされたモデルの変種に対して、その目標の追求に関与するメカニズムが特定されました。興味深いことに、これらのメカニズムはモデルの「アシスタント」ペルソナの内部表現に埋め込まれていました。
ここで、アトリビューショングラフに関心ができた、では、例えばGPT-2でアトリビューショングラフが作れないかと言うのが今回のテーマとなる。 さすがに、GPT-4oのようなモデルは中身が公開されていないし、公開されているとしても、DeepSeek-R1などはモデル規模が大きすぎ、そもそも、処理が重すぎる。 それで、古典的なモデルであるGPT-2に目を付けた。 そのため、以下のようなコードを実行した。
import torch
from transformers import GPT2Model, GPT2Tokenizer
import matplotlib.pyplot as plt
import seaborn as sns
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2Model.from_pretrained("gpt2", attn_implementation="eager")
text = "The quick brown fox jumps over the lazy dog."
tokens = tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = model(**tokens, output_attentions=True)
attentions = outputs.attentions # これはリスト形式で各層のAttentionを含む
# 例えば、最後の層のAttentionを可視化
layer_idx = -1
head_idx = 0 # 最初のAttentionヘッドを選択
attention_matrix = attentions[layer_idx][0, head_idx].cpu().numpy()
plt.figure(figsize=(8, 8))
sns.heatmap(attention_matrix, cmap="viridis", xticklabels=tokens["input_ids"][0], yticklabels=tokens["input_ids"][0])
plt.xlabel("Attention対象")
plt.ylabel("Attention元")
plt.title("GPT-2のAttentionマップ")
plt.show()
このコードによって、以下のように最後の層のAttentionが可視化される。
しかし、この可視化では、目盛りがテンソルのインデックスのため、意味が分かりません。従って、目盛りを変えます。
# トークンIDを対応する単語へ変換
tokens_list = tokenizer.convert_ids_to_tokens(tokens["input_ids"][0].tolist())
# 最後の層、最初のAttentionヘッドを取得
layer_idx = -1
head_idx = 0
attention_matrix = attentions[layer_idx][0, head_idx].cpu().numpy()
plt.figure(figsize=(8, 8))
sns.heatmap(attention_matrix, cmap="viridis", xticklabels=tokens_list, yticklabels=tokens_list)
plt.xlabel("Attention対象")
plt.ylabel("Attention元")
plt.title("GPT-2のAttentionマップ")
plt.xticks(rotation=90) # 横軸のラベルを縦向きにすることで見やすくする
plt.show()
ここから、the fox辺りが注意を引いていることが判ります。
今後の方向性としては、特に主語と動詞の関係や、形容詞と名詞など係り受けの関係などとの関わりを異なるテキスト試すのはありかなと思います。あと、他の層ではどうなるか。