BERT のようなTransformerベースの事前学習モデルは、大量のテキストから自動的に言語知識を獲得します。ファインチューニングすれば、感情分析、質問応答、固有表現抽出など多様なタスクで高い性能を発揮します。しかし、ここで素朴な疑問が湧きます — BERT は「何を」知っているのでしょうか? 12層(あるいは24層)のTransformerの内部で、品詞の情報はどこに蓄えられているのか、文の構文木を表現できているのか、主語と動詞の数の一致のようなルールを理解しているのか。こうした問いに答えようとする研究分野がBERTologyです。
BERTology はモデルの性能を上げる研究ではなく、モデルの内部表現を解剖する研究です。医学に例えれば、MRI で脳の特定領域がどの認知機能に対応しているかを調べるようなものです。脳の各部位が言語、視覚、運動に分担して関わるように、BERT の各層や各ヘッドも異なる言語的役割を担っている可能性があります。その「役割分担」を明らかにする主要なツールがProbing(プロービング)タスクです。
BERTology と Probing を理解することは、以下のような場面で直接役立ちます。
- モデルの選択と圧縮: 各層がどの情報を持つかがわかれば、不要な層を削除するプルーニングや知識蒸留で、どの層を残すべきか根拠のある判断ができます。たとえば「構文情報は中間層に集中する」とわかっていれば、構文タスクに使う蒸留モデルで中間層を優先的に保持できます
- ファインチューニング戦略の最適化: 層ごとの情報分布がわかれば、全層を更新するのではなく、タスクに関連の深い層だけを選択的にファインチューニングする戦略が取れます
- モデルの信頼性評価: Probing で「このモデルは数の一致を本当に理解しているか」を検証できれば、高リスク領域での採用判断に客観的な根拠を与えられます
- 新しいアーキテクチャの設計指針: 既存モデルの内部を分析して「何が足りないか」がわかれば、次世代モデルの設計に活かせます
本記事の内容
- BERTology の全体像と Probing タスクの位置づけ
- Probing の基本設計 — 固定表現+線形分類器
- 層ごとの情報分布と「言語知識の階層構造」
- 構文的 Probing(Hewitt & Manning の Structural Probe)
- 意味的 Probing(意味役割ラベリング、語義曖昧性解消)
- Probe 自体の表現力問題と Control Tasks
- BERT の各層・各ヘッドが担う言語知識の分析
- Python での簡易 Probing 実装
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
BERTology とは何か
なぜモデルの内部を調べるのか
ディープラーニングモデルは強力ですが、「なぜうまくいくのか」の理解は性能の進歩に追いついていません。BERT が品詞タグ付けで高い精度を出すとき、それはモデルが品詞の概念を「理解」しているからなのか、それとも表層的なパターン(たとえば「-ly で終わる単語は副詞」)を記憶しているだけなのか。この問いは単なる知的好奇心ではなく、モデルの信頼性や汎化能力に直結する実用的な問題です。
BERTology は Devlin et al. (2019) の BERT 論文をきっかけに爆発的に広がった研究領域で、Rogers et al. (2020) の “A Primer in BERTology” というサーベイ論文がこの分野の名称を定着させました。BERTology の研究は大きく3つのアプローチに分類できます。
- Probing(プロービング): モデルの内部表現を固定し、簡単な分類器で言語知識をテストする
- Attention 分析: 注意重みのパターンから、各ヘッドや各層が捉える言語構造を調べる
- 摂動実験: 入力や内部表現を意図的に変化させ、出力への影響を観察する
このうち最も体系的で広く使われているのが Probing です。Attention 分析は直感的ですが、前の記事(Attentionの解釈性と限界)で見たように、注意重みが必ずしもモデルの判断根拠を反映しない問題があります。Probing は注意重みに頼らず、各層の出力表現そのものに含まれる情報を直接テストできるため、より信頼性の高いアプローチとされています。
BERTology の研究テーマ
BERTology では、以下のような具体的な問いが探求されています。
- 構文知識: BERT は品詞、構文木、依存関係を表現しているか?
- 意味知識: 語義の区別、意味役割、含意関係を捉えているか?
- 世界知識: 「パリはフランスの首都である」のような事実的知識を持っているか?
- 数の一致: 「The keys to the cabinet are on the table」(主語は keys で複数形)のような長距離の文法的一致を追跡できるか?
- 層の役割分担: 低層・中間層・高層で異なる種類の情報を担当しているか?
- ヘッドの専門化: 特定の注意ヘッドが特定の言語関係(たとえば形容詞→名詞)を担当しているか?
こうした問いに体系的に答えるための主要な道具が、次に紹介する Probing タスクです。
Probing タスクの基本設計
直感的なイメージ
Probing タスクのアイデアは驚くほどシンプルです。イメージとしては、学生の理解度を測るために選択式テストを課すようなものです。学生(BERT)が授業(事前学習)で何を学んだかを知りたいとき、自由記述のテストを出すと、学生の文章力や表現力がスコアに影響してしまい、純粋な理解度を測れません。そこで、問いの難易度を最低限に設定した選択式テスト(線形分類器)を使います。このテストで高いスコアが出れば、「学生はその知識を持っている」と結論できるわけです。
もう一つのアナロジーとして、脳科学の研究手法があります。fMRI で脳の活動パターンを記録し、そこから被験者が見ている画像のカテゴリ(顔、家、風景など)を予測できるかを調べるデコーディング解析があります。Probing はこれの NLP 版です。BERT の内部表現(脳の活動パターンに相当)から、品詞や構文関係(認知カテゴリに相当)をデコードできるかを調べます。
形式的な定義
Probing タスクの手続きを数式で定義しましょう。事前学習済みモデル $f$ が入力テキスト $\bm{x} = (x_1, x_2, \dots, x_n)$ を受け取り、各層 $\ell$ での隠れ表現 $\bm{h}_i^{(\ell)} \in \mathbb{R}^d$ を出力するとします。ここで $d$ は隠れ層の次元数(BERT-Base なら $d = 768$)、$i$ はトークンのインデックスです。
Probing の手順は以下の3ステップです。
ステップ1: 表現の抽出
事前学習済みモデルにテキストを入力し、指定した層 $\ell$ の隠れ表現 $\bm{h}_i^{(\ell)}$ を取得します。このとき、モデルのパラメータは完全に固定します。勾配計算も行いません。モデルを単なる「特徴量抽出器」として扱います。
ステップ2: Probe(分類器)の学習
抽出した表現を入力として、別途用意した簡単な分類器(probe)$g$ を学習します。言語ラベル $y_i$(品詞、構文関係など)がアノテーションされたデータセットを使い、次の最適化問題を解きます。
$$ \min_{\bm{W}, \bm{b}} \sum_{i} \mathcal{L}\left(g(\bm{h}_i^{(\ell)}; \bm{W}, \bm{b}), \; y_i\right) $$
ここで $\mathcal{L}$ は損失関数(分類ならクロスエントロピー)です。最もシンプルな probe は線形分類器で、次のように定義されます。
$$ g(\bm{h}; \bm{W}, \bm{b}) = \text{softmax}(\bm{W}\bm{h} + \bm{b}) $$
$\bm{W} \in \mathbb{R}^{C \times d}$、$\bm{b} \in \mathbb{R}^C$ で、$C$ はラベルの種類数です。
ステップ3: 性能の評価
テストデータで probe の精度を測定します。精度が高ければ、その層の表現には当該の言語情報が含まれている(デコード可能な形で符号化されている)と解釈します。
なぜ Probe は「簡単な」分類器でなければならないのか
ここに Probing タスク設計の核心があります。もし probe として10層のニューラルネットワークを使ったら、probe 自身がデータから言語知識を学習してしまい、元の表現に知識が含まれていたのか、probe が新たに学んだのかを区別できなくなります。
たとえば、ランダムに初期化した埋め込み(言語知識を一切持たない)を入力しても、十分に強力な probe なら品詞分類で高い精度を出せてしまうかもしれません。これでは「表現に品詞の情報が含まれている」とは言えません。
線形分類器を使う理由は、表現空間の線形分離可能性をテストしたいからです。情報が線形分離可能な形で表現に埋め込まれているということは、その情報が「表面近く」に存在しており、表現がその言語的特性を明示的に符号化していると解釈できます。
数式で書くと、線形 probe が高い精度を出すということは、隠れ表現の空間 $\mathbb{R}^d$ に超平面 $\bm{w}^\top \bm{h} + b = 0$ が存在し、この超平面で異なる言語ラベルを分離できるということです。
ただし、線形分類器が分離できないからといって、情報が存在しないとは限りません。非線形に符号化されている可能性もあります。この問題については、MLP probe(1〜2層の非線形分類器)を使う研究もありますが、probe の表現力と「情報の検出」のトレードオフは後のセクションで詳しく議論します。
ここまでで Probing の基本的なフレームワークを理解しました。次に、この手法を BERT の各層に適用すると何がわかるのか — 層ごとの情報分布を見ていきましょう。
層ごとの情報分布
言語知識の階層構造
BERT のような深いTransformerモデルでは、情報処理に階層構造があることが知られています。これはコンピュータビジョンにおける CNN の振る舞いとよく似ています。CNN では低層がエッジや色を、中間層がテクスチャやパーツを、高層がオブジェクト全体を捉えます。同様に、BERT でも層の深さによって捉える言語的情報の種類が変わります。
Jawahar et al. (2019) や Tenney et al. (2019) の研究により、BERT-Base(12層)の各層が持つ情報は概ね以下のような階層構造を形成していることがわかっています。
| 層の範囲 | 主に含まれる情報 | 対応する言語学的レベル |
|---|---|---|
| 第1〜4層(低層) | 品詞(POS)、形態素情報、局所的な語順 | 形態論・音韻論 |
| 第5〜8層(中間層) | 構文木構造、依存関係、句構造 | 統語論 |
| 第9〜12層(高層) | 意味役割、共参照、事実的知識 | 意味論・語用論 |
この階層構造は、言語学のレベルとおおむね対応しています。自然言語を理解するには、まず単語の品詞を把握し(形態論)、次に文の構造を組み立て(統語論)、最後に意味を解釈する(意味論)という段階を踏みます。BERT の内部もこれに近い「処理パイプライン」を自然に学習しているわけです。
層ごとの Probing 結果
具体的な Probing の結果を見てみましょう。Tenney et al. (2019) の “BERT Rediscovers the Classical NLP Pipeline” では、BERT の各層に対して複数の言語タスクで Probing を行い、タスクごとに「どの層で精度が最も向上するか」を測定しました。
彼らはmixing weightという指標を導入しました。これは各層 $\ell$ の貢献度を表すスカラー重み $s_\ell$ で、全層の表現を加重和した入力に対して probe を学習します。
$$ \bm{h}_i^{\text{mix}} = \sum_{\ell=0}^{L} s_\ell \bm{h}_i^{(\ell)} $$
ここで重み $s_\ell$ は softmax で正規化されます。
$$ s_\ell = \frac{\exp(\alpha_\ell)}{\sum_{k=0}^{L} \exp(\alpha_k)} $$
$\alpha_\ell$ は学習可能なスカラーパラメータです。学習後に各 $s_\ell$ の値を見ることで、「このタスクにはどの層の情報が最も寄与しているか」がわかります。
結果として、品詞タグ付け(POS tagging)では低層に重みが集中し、依存関係解析(dependency parsing)では中間層に、意味役割ラベリング(semantic role labeling)では高層に重みが集中しました。これは古典的な NLP パイプライン(トークン化 → 品詞タグ付け → 構文解析 → 意味解析)の順序とほぼ一致しており、BERT が古典的な NLP パイプラインを内部的に「再発見」していることを示唆しています。
埋め込み層と最終層の特殊性
興味深いことに、最初の層(埋め込み層、第0層)と最終層(第12層)はやや特殊な振る舞いを見せます。
埋め込み層(第0層)は WordPiece トークンの埋め込みと位置埋め込みの和です。この段階ではまだ文脈情報が統合されていないため、文脈依存の情報(構文、意味)はほとんど含まれていません。しかし、単語の基本的な意味(Word2Vec 的な静的埋め込みに近い情報)はここで最も純粋に取得できます。
最終層(第12層)は事前学習の損失関数(MLM, NSP)に直接最適化されているため、次の単語予測やペア文判定に必要な情報が集約されます。しかし、この最適化の副作用として、中間層に豊富に存在していた構文的情報が最終層ではやや薄れることが報告されています。ファインチューニングされたモデルでは、最終層がさらにタスク特化の情報で上書きされるため、汎用的な言語知識は中間層に最も良い形で残っていることが多いです。
こうした層ごとの情報分布の知見は、Probing の基本的な適用例です。ここからは、より洗練された Probing 手法について掘り下げていきます。まず、構文構造を直接的に探る Structural Probe を見てみましょう。
構文的 Probing — Hewitt & Manning の Structural Probe
構文木を表現空間に見出す
品詞のような離散的なラベルの Probing は比較的単純です。しかし、文の構文木(parse tree)のような構造的な情報は、単一のラベルでは表現できません。構文木は単語間の親子関係と、その階層的な距離を持つグラフ構造です。BERT の内部表現に構文木の情報が埋め込まれているかを調べるには、従来の「分類器で予測する」アプローチでは不十分です。
Hewitt & Manning (2019) は、この問題を解決する巧妙な手法 Structural Probe を提案しました。彼らの核心的なアイデアは次の通りです — もし BERT の表現空間のどこかに構文木が埋め込まれているなら、適切な線形変換を施すことで、変換後の空間でのユークリッド距離が構文木上の距離と一致するはずだ。
数学的定式化
構文木上の距離を定義しましょう。文 $\bm{x} = (x_1, \dots, x_n)$ の構文木 $T$ について、単語 $x_i$ と $x_j$ の木上の距離 $d_T(x_i, x_j)$ は、$T$ 上で $x_i$ から $x_j$ へ至る最短パスのエッジ数です。
Structural Probe は、線形変換 $\bm{B} \in \mathbb{R}^{k \times d}$ を学習し、変換後の表現空間でのユークリッド距離二乗が構文木上の距離を近似するように最適化します。
$$ d_B(\bm{h}_i^{(\ell)}, \bm{h}_j^{(\ell)})^2 = (\bm{h}_i^{(\ell)} – \bm{h}_j^{(\ell)})^\top \bm{B}^\top \bm{B} (\bm{h}_i^{(\ell)} – \bm{h}_j^{(\ell)}) $$
ここで $\bm{B}^\top \bm{B}$ は半正定値行列であり、これは隠れ表現空間上にマハラノビス距離を定義していることに他なりません。$k < d$ とすることで、$\bm{B}$ は高次元空間から低次元の部分空間への射影を行い、構文に無関係な次元を除去する効果を持ちます。
学習の目的関数は、全ての単語ペアについて、変換後の距離と構文木上の距離の差を最小化するものです。
$$ \min_{\bm{B}} \sum_{(i, j)} \left| d_B(\bm{h}_i^{(\ell)}, \bm{h}_j^{(\ell)}) – d_T(x_i, x_j) \right| $$
木の深さの Probe
Hewitt & Manning は距離の Probe に加えて、構文木における深さ(depth) の Probe も提案しました。単語 $x_i$ の木上の深さ $\text{depth}_T(x_i)$ は、根ノードから $x_i$ までのパスのエッジ数です。
深さの Probe は、線形変換後のノルム二乗で深さを近似します。
$$ \|\bm{B}\bm{h}_i^{(\ell)}\|^2 \approx \text{depth}_T(x_i) $$
この式が成り立つということは、構文木の根に近い単語ほど変換後のベクトルのノルムが小さく、葉に近い単語ほどノルムが大きくなるように情報が符号化されていることを意味します。
実験結果の意義
Hewitt & Manning の実験結果は印象的でした。BERT-Large の中間層(第16層付近)で学習した Structural Probe は、構文木上の距離を高い精度で再現しました。評価指標としてUUAS(Undirected Unlabeled Attachment Score)を使います。これは、Probe が予測した距離から最小全域木を構築し、正解の構文木とどれだけ一致するかを測る指標です。BERT の表現は、ベースラインの埋め込み(ELMo や Word2Vec)を大きく上回る UUAS を達成しました。
この結果は、BERT の内部表現が構文木の構造を線形変換でアクセス可能な形で符号化していることを示しています。BERT は構文解析のタスクで明示的に訓練されたわけではないにもかかわらず、事前学習の過程で構文的な知識を自然に獲得しているのです。
構文的な Probing では、文の構造が BERT の内部にどう表現されているかを見てきました。では、語義の区別や意味的な関係はどうでしょうか。次は意味的な Probing に目を向けます。
意味的 Probing
語義曖昧性解消(Word Sense Disambiguation)
語義曖昧性解消(WSD)は、多義語の正しい意味を文脈から判定するタスクです。たとえば日本語の「はし」は「橋」「箸」「端」と複数の意味を持ち、英語の “bank” も “river bank”(川岸)と “investment bank”(投資銀行)では意味が全く異なります。
BERT 以前の静的埋め込み(Word2Vec, GloVe)は、各単語に1つの固定ベクトルしか割り当てないため、語義の区別が原理的に不可能でした。BERT は文脈に応じて異なる表現を生成するため、語義の情報を自然に持つことが期待されます。
Probing による検証では、BERT の隠れ表現から語義ラベル(WordNet のセンス ID など)を線形分類器で予測できるかをテストします。Reif et al. (2019) は、BERT の中間〜高層の表現を使った Probing で、同じ単語でも異なる文脈では表現空間上で明確にクラスタが分離していることを示しました。
具体的には、”bank” の「金融機関」としての出現と「川岸」としての出現について、BERT の各層での表現ベクトルを抽出し、PCA や t-SNE で可視化すると、中間層以降で2つのクラスタが明確に分離します。これは BERT が文脈を考慮して多義語の表現を動的に変えていることの直接的な証拠です。
意味役割ラベリング(Semantic Role Labeling)
意味役割ラベリング(SRL)は、文中の各要素が述語に対してどのような意味的役割(動作主、被動作主、場所、時間など)を持つかを同定するタスクです。たとえば「猫が魚を食べた」では、「猫」が動作主(Agent)、「魚」が被動作主(Theme)、「食べた」が述語(Predicate)です。
Tenney et al. (2019) の Probing 実験では、SRL のラベルは BERT の高層(第9〜12層)で最もよく予測できることがわかりました。これは直感と合致します。意味役割を判定するには、まず各単語の品詞を知り(低層)、文の構文構造を把握し(中間層)、その上で意味的な関係を推論する(高層)必要があるからです。
共参照解析
共参照解析は、テキスト中で同一のエンティティを指す異なる表現(「バラク・オバマ」「彼」「大統領」など)を同定するタスクです。これは文をまたがる理解が必要であり、高度な意味的処理を要します。
BERT の表現を使った共参照 Probing でも、高層の表現が最も高い精度を示します。さらに興味深いのは、同一エンティティを指す表現のベクトルが、BERT の高層では表現空間上で近くに配置される傾向があることです。これは BERT が、表面的には異なるテキスト表現であっても、同一のエンティティを指すものは類似した内部表現に変換していることを示しています。
世界知識の Probing
Petroni et al. (2019) の “Language Models as Knowledge Bases” では、BERT の内部に事実的な世界知識がどの程度蓄えられているかを調べました。具体的には、「パリは[MASK]の首都である」のような穴埋め文を BERT に与え、マスク位置の予測精度を測定します。
この手法は Probing の変種であり、追加の分類器を学習する代わりに、BERT 自身の MLM ヘッドを使って知識をテストします。結果は驚くべきもので、BERT は「バイオリンの音色は[MASK]によって大きく左右される」→「木材」、「ダンテは[MASK]で生まれた」→「フィレンツェ」のように、事実的知識を高い精度で再現できました。一部のリレーション(誕生地、職業など)では、知識ベースの検索に匹敵する精度を示しています。
ここまでで、Probing タスクが構文から意味、さらには世界知識まで幅広い言語情報を検出できることを見てきました。しかし、ここで重要な疑問が生じます — Probe の高い精度は、本当に元の表現に情報が含まれていることの証拠と言えるのでしょうか? 次のセクションでは、この根本的な問題に取り組みます。
Probe 自体の表現力問題と Control Tasks
高い Probing 精度の落とし穴
Probing で高い精度が得られたとき、「この層の表現にはその言語情報が含まれている」と即座に結論したくなります。しかし、ここには重大な落とし穴があります。
Hewitt & Liang (2019) は “Designing and Interpreting Probes with Control Tasks” で、この問題を明確に指摘しました。彼らの核心的な主張は次の通りです — Probing の精度が高いのは、表現に情報が含まれているからではなく、probe(分類器)自身が学習の過程でタスクを「記憶」しているだけかもしれない。
具体的に考えてみましょう。品詞タグ付け(45クラス分類)の Probing を行うとします。BERT の表現次元は768次元です。768次元の空間は非常に高次元であり、線形分類器でさえ45クラスを分離するための超平面を見つける自由度が十分にあります。もしランダムな表現でもある程度の精度が出るなら、BERT の表現で高い精度が出ても、それが BERT 固有の言語知識を反映しているとは限りません。
Control Tasks(対照タスク)
Hewitt & Liang はこの問題に対処するために Control Tasks という方法を提案しました。Control Tasks のアイデアは非常に巧妙です。
通常の Probing タスク(linguistic task)に対して、以下の性質を持つ対照タスク(control task)を設計します。
- 入力は同じ: BERT の同じ隠れ表現を使う
- 出力ラベルの構造は同じ: ラベルの数やクラスバランスが同じ
- 言語的な意味がない: ラベルの割り当てがランダム(各単語型に一意にランダムラベルを割り当てる)
たとえば、品詞タグ付けの Control Task は以下のように構成します。語彙中の各単語型(type)に対して、45種類の品詞ラベルの代わりに、45種類のランダムラベルを一意に割り当てます。各単語型へのラベル割り当ては決定的ですが、言語的な規則性は一切ありません。
Selectivity — 意味のある精度を測る
Control Task を導入したことで、Probing の精度を正しく解釈するための指標 Selectivity が定義できます。
$$ \text{Selectivity} = \text{Acc}_{\text{linguistic}} – \text{Acc}_{\text{control}} $$
ここで $\text{Acc}_{\text{linguistic}}$ は通常の言語タスクでの Probing 精度、$\text{Acc}_{\text{control}}$ は Control Task での精度です。
この差分が Selectivity の鍵です。意味を解釈すると次のようになります。
- Selectivity が高い: 言語タスクでの精度は高いが、Control Task での精度は低い。これは probe が BERT の表現に含まれる言語的な構造を利用していることを示します。表現に言語知識が含まれているという証拠が強くなります
- Selectivity が低い: 言語タスクでも Control Task でも精度が高い。これは probe が表現の高次元性を利用して任意のラベルを「記憶」できてしまっていることを意味します。表現に言語知識が含まれているかは不明です
Hewitt & Liang の実験では、線形 probe は高い Selectivity を示しましたが、MLP probe(2層の非線形分類器)は Selectivity が低くなる傾向がありました。MLP は表現力が高い分、Control Task でも高い精度を出してしまい、言語的な情報の検出とタスクの記憶を区別しにくくなるのです。
Probe の複雑さと情報の検出のトレードオフ
この結果は、probe の設計に関する重要な教訓を与えてくれます。
線形 probe の利点は明確です。表現力が制限されているため、高い精度が出れば、それは表現に線形分離可能な形で情報が埋め込まれている証拠になります。Selectivity も高くなりやすく、解釈が容易です。一方、欠点は、非線形に符号化された情報を検出できないことです。
MLP probe は非線形な情報も検出できますが、probe 自体が情報を「学習」してしまうリスクがあります。Selectivity が低くなりやすく、結果の解釈に注意が必要です。
現在の研究コミュニティでは、以下のアプローチが推奨されています。
- まず線形 probe で調べる(最も保守的な推定)
- 線形 probe で精度が低い場合、MLP probe を試す
- MLP probe を使う場合は、必ず Control Task も実施して Selectivity を報告する
- MDL(Minimum Description Length) に基づく probe(Voita & Titov, 2020)— probe の圧縮コストを考慮し、「表現にどれだけの情報が含まれているか」をビット数で定量化する
ここまでで、Probing の基本設計と、結果を正しく解釈するための方法論(Control Tasks, Selectivity)を理解しました。次に、BERT の各層と各ヘッドがどのような言語知識を担っているかについて、これまでの研究で明らかになった知見をまとめます。
BERT の各層・各ヘッドの言語知識分析
層の役割分担 — 全体像
これまでのセクションで部分的に触れてきた層ごとの役割分担を、ここで体系的に整理します。Rogers et al. (2020) のサーベイ “A Primer in BERTology” を中心に、主要な知見をまとめます。
低層(第1〜4層) は、主に表層的・形態論的な情報を処理します。
- 品詞(POS)の情報が最も強く符号化される(Tenney et al., 2019)
- 単語の形態素的特徴(接尾辞、語幹など)を捉える
- 局所的な語順パターンを学習する
- 静的な単語埋め込み(Word2Vec)に近い表現を持つ
中間層(第5〜8層) は、構文的な情報の処理が中心です。
- 依存関係解析の精度が最も高くなる(Hewitt & Manning, 2019)
- 構文木の構造が線形変換でアクセス可能な形で符号化される
- 主語-動詞の一致(subject-verb agreement)の情報が最も豊富
- 名詞句や動詞句などの句構造のバウンダリを捉える
高層(第9〜12層) は、意味的な情報とタスク固有の情報を処理します。
- 意味役割ラベリング(SRL)の精度が最も高くなる
- 共参照解析の情報が豊富
- ファインチューニング時にはタスク固有の情報が集約される
- 事前学習の損失関数(MLM)に最適化された「タスク特化」の表現
注意ヘッドの専門化
Clark et al. (2019) の “What Does BERT Look At?” では、BERT の各注意ヘッドが特定の言語関係を専門的に捉えていることが報告されました。以下は特に顕著な例です。
- 第2層の一部のヘッド: 隣接する単語への注意(バイグラムパターン)
- 第4〜6層の特定のヘッド: 直接目的語 → 動詞 の依存関係
- 第8〜9層の特定のヘッド: 共参照関係(代名詞 → 先行詞)
- 多くのヘッド(特に低層): [SEP] トークンへの強い注意(「情報の捨て場」として機能)
特に興味深いのは、[SEP] トークンへの注意パターンです。多くのヘッドで、全てのトークンが [SEP] に高い注意を向けるパターンが観察されます。これは [SEP] が「情報を持たないデフォルトの注意先」として機能しているためと考えられています。あるヘッドが特定の入力に対して有用な関係を見つけられない場合、[SEP] に注意を集中させることで、出力への影響を最小限に抑えているのです。
Kovaleva et al. (2019) の5つの注意パターン
Kovaleva et al. (2019) は、BERT の注意パターンを体系的に分析し、以下の5つの典型的なパターンを同定しました。
- Vertical(垂直): 全トークンが特定のトークン([CLS] や [SEP])に注目
- Diagonal(対角): 各トークンが自分自身に注目
- Vertical + Diagonal(垂直+対角): 上記2つの組み合わせ
- Block(ブロック): 文内のトークン同士に注目(文をまたがない)
- Heterogeneous(異種混合): 明確なパターンを持たず、言語的に意味のある注意が含まれる可能性が高い
この分類は重要な含意を持ちます。Vertical や Diagonal のパターンは「機能的に冗長」であり、ヘッドの枝刈り(pruning)の候補になります。実際、Voita et al. (2019) や Michel et al. (2019) は、BERT のヘッドの大部分を除去しても性能がほとんど低下しないことを示しました。Heterogeneous パターンを持つ少数のヘッドが、モデルの性能に不可欠な情報を担っているのです。
数の一致と長距離依存
Goldberg (2019) は、BERT が主語-動詞の数の一致(subject-verb agreement)をどの程度追跡できるかを Probing で検証しました。たとえば以下のような文を考えます。
- “The key to the cabinets is on the table.”(主語は “key” で単数)
- “The keys to the cabinet are on the table.”(主語は “keys” で複数)
人間でも “cabinets”(複数形)に引きずられて間違えやすいこの種の文で、BERT は高い精度で正しい動詞の形を予測できました。さらに、主語と動詞の間に挟まれる要素(”to the cabinets”)が長くなっても、精度の低下は比較的穏やかでした。
この能力は Probing で確認すると、中間層(第5〜8層)で最も強く符号化されていることがわかります。構文的な依存関係の追跡が中間層の主要な役割であるという先述の知見と整合します。
ここまでで、BERTology の理論的な枠組みと主要な研究知見を一通り概観しました。最後に、これらの考え方を Python で実際に実装し、手を動かして確認してみましょう。
Python での簡易 Probing 実装
実装の全体方針
ここでは、BERT の各層から隠れ表現を抽出し、品詞タグ付け(POS tagging)の Probing タスクを実装します。以下のステップで進めます。
- Hugging Face の transformers ライブラリで BERT から全層の隠れ表現を抽出
- NLTK で品詞ラベルを取得し、学習データを構築
- 各層ごとにロジスティック回帰(線形 probe)を学習
- 層ごとの精度を比較し、品詞情報がどの層に集中しているかを確認
BERT からの隠れ表現の抽出
まず、BERT に文を入力して全層の隠れ表現を取得する関数を実装します。
import torch
import numpy as np
from transformers import BertTokenizer, BertModel
# モデルとトークナイザの読み込み
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased", output_hidden_states=True)
model.eval()
def extract_hidden_states(sentence):
"""文を入力し、全層の隠れ表現を抽出する"""
inputs = tokenizer(sentence, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
outputs = model(**inputs)
# hidden_states: (num_layers+1, batch, seq_len, hidden_dim)
# 第0層 = 埋め込み層、第1〜12層 = Transformerの各層
hidden_states = outputs.hidden_states
# テンソルのリストをNumPy配列に変換
# 形状: (13, seq_len, 768) — 13 = 埋め込み層 + 12層
all_layers = np.array([h.squeeze(0).numpy() for h in hidden_states])
# トークンのリスト(サブワード単位)
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"].squeeze(0))
return all_layers, tokens
# テスト
sentence = "The quick brown fox jumps over the lazy dog"
all_layers, tokens = extract_hidden_states(sentence)
print(f"トークン: {tokens}")
print(f"隠れ表現の形状: {all_layers.shape}")
print(f" - 層数(埋め込み層含む): {all_layers.shape[0]}")
print(f" - 系列長: {all_layers.shape[1]}")
print(f" - 隠れ次元: {all_layers.shape[2]}")
上のコードでは、output_hidden_states=True を指定することで、BERT の全13層(埋め込み層 + 12層の Transformer 層)の出力を取得しています。torch.no_grad() でモデルのパラメータを固定し、Probing の原則に従って特徴量抽出のみを行っています。出力の形状は (13, seq_len, 768) で、各層・各トークンの768次元の隠れ表現が得られます。
品詞ラベルの付与とサブワードのアライメント
Probing 用のデータセットを構築するために、品詞ラベルを取得し、BERT のサブワードトークンと対応付ける処理が必要です。BERT は WordPiece トークナイザを使うため、1つの単語が複数のサブワードに分割されることがあります。この場合、元の単語の品詞ラベルをサブワードの先頭トークンに割り当てます。
import nltk
nltk.download('averaged_perceptron_tagger_eng', quiet=True)
nltk.download('universal_tagset', quiet=True)
def get_pos_labels(sentence):
"""文に品詞タグを付与(Universal Tagsetを使用)"""
words = sentence.split()
# Universal Tagsetの品詞タグを取得
pos_tags = nltk.pos_tag(words, tagset='universal')
return pos_tags
def align_subwords_to_pos(sentence, tokens, pos_tags):
"""
サブワードトークンと品詞ラベルを対応付ける。
サブワードの先頭トークンにのみラベルを割り当てる。
"""
aligned = []
word_idx = 0
for i, token in enumerate(tokens):
# [CLS], [SEP] はスキップ
if token in ["[CLS]", "[SEP]", "[PAD]"]:
continue
# サブワードの先頭トークン('##'で始まらない)
if not token.startswith("##"):
if word_idx < len(pos_tags):
aligned.append({
"token_idx": i,
"token": token,
"word": pos_tags[word_idx][0],
"pos": pos_tags[word_idx][1],
})
word_idx += 1
# '##' で始まるサブワードはスキップ(先頭トークンのラベルを使用)
return aligned
# テスト
sentence = "The quick brown fox jumps over the lazy dog"
pos_tags = get_pos_labels(sentence)
all_layers, tokens = extract_hidden_states(sentence)
aligned = align_subwords_to_pos(sentence, tokens, pos_tags)
print("品詞ラベルの対応付け:")
for item in aligned:
print(f" {item['word']:10s} -> {item['pos']:5s} (トークン: {item['token']})")
この処理のポイントは、サブワード分割への対応です。たとえば “jumping” が [“jump”, “##ing”] に分割される場合、品詞ラベルは “jump” にのみ割り当て、”##ing” は無視します。Universal Tagset を使うことで、品詞ラベルを12種類の粗いカテゴリ(NOUN, VERB, ADJ, ADV, DET など)に統一し、学習データが少なくても Probing が安定するようにしています。
学習データの構築
複数の文から Probing 用のデータセットを構築します。
from sklearn.preprocessing import LabelEncoder
# Probing用のサンプル文(実際の研究ではPenn Treebank等を使用)
sentences = [
"The quick brown fox jumps over the lazy dog",
"A large black cat sleeps on the warm mat",
"She quickly ran to the old wooden door",
"The young students studied hard for the difficult exam",
"My small red car needs a new battery soon",
"They carefully built a beautiful stone wall yesterday",
"The bright morning sun rises above the tall mountains",
"Several happy children played outside during the warm afternoon",
"He slowly walked through the dark narrow alley alone",
"Our new teacher gave an interesting lecture today",
"The heavy rain flooded many streets in the city",
"Two curious dogs chased a frightened cat up the tree",
"She always reads her favorite book before sleeping peacefully",
"The old farmer grows fresh vegetables in his garden",
"A tiny bird sang a sweet melody from the branch",
"The experienced doctor examined the patient very thoroughly",
"Many tall buildings stand along the busy main street",
"He often writes long letters to his distant friends",
"The cold winter wind blew hard across the open field",
"Young scientists discovered a remarkable new species last year",
]
def build_probing_dataset(sentences):
"""複数の文からProbing用データセットを構築"""
# 全層の特徴量を格納: layer_features[layer] = list of vectors
layer_features = {layer: [] for layer in range(13)}
labels = []
for sentence in sentences:
all_layers, tokens = extract_hidden_states(sentence)
pos_tags = get_pos_labels(sentence)
aligned = align_subwords_to_pos(sentence, tokens, pos_tags)
for item in aligned:
idx = item["token_idx"]
labels.append(item["pos"])
for layer in range(13):
layer_features[layer].append(all_layers[layer, idx])
# NumPy配列に変換
for layer in range(13):
layer_features[layer] = np.array(layer_features[layer])
# ラベルを数値に変換
le = LabelEncoder()
y = le.fit_transform(labels)
return layer_features, y, le
layer_features, y, label_encoder = build_probing_dataset(sentences)
print(f"データ数: {len(y)}")
print(f"品詞クラス数: {len(label_encoder.classes_)}")
print(f"品詞ラベル: {list(label_encoder.classes_)}")
print(f"各層の特徴量形状: {layer_features[0].shape}")
上のコードでは、20文からトークンレベルの特徴量とラベルのペアを抽出しています。実際の研究では Penn Treebank や Universal Dependencies のような大規模なアノテーション付きコーパスを使いますが、ここでは Probing の仕組みを理解するために小規模なデータで実装しています。各層の特徴量がリストとして格納され、全てのサンプルで同じラベルベクトル y を共有する構造になっています。
層ごとのロジスティック回帰による Probing
各層の表現に対してロジスティック回帰(線形 probe)を学習し、精度を比較します。
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
def probe_all_layers(layer_features, y, cv=5):
"""各層でロジスティック回帰のProbing精度を計測"""
accuracies = []
for layer in range(13):
X = layer_features[layer]
# ロジスティック回帰(線形probe)
clf = LogisticRegression(
max_iter=1000,
solver='lbfgs',
multi_class='multinomial',
C=1.0, # 正則化パラメータ
)
# 交差検証で精度を評価
scores = cross_val_score(clf, X, y, cv=cv, scoring='accuracy')
mean_acc = scores.mean()
std_acc = scores.std()
accuracies.append((mean_acc, std_acc))
layer_name = "Embedding" if layer == 0 else f"Layer {layer}"
print(f"{layer_name:12s}: {mean_acc:.3f} (+/- {std_acc:.3f})")
return accuracies
print("=== POS Tagging Probing (Linear Probe) ===\n")
accuracies = probe_all_layers(layer_features, y)
ロジスティック回帰は sklearn の LogisticRegression を使い、multi_class='multinomial' でソフトマックス分類を行います。正則化パラメータ $C = 1.0$ は、probe の複雑さを適度に制御します。交差検証(5-fold)で精度を評価することで、過学習のリスクを軽減しています。
結果の可視化
層ごとの Probing 精度をグラフで可視化します。
fig, ax = plt.subplots(figsize=(10, 6))
layers = list(range(13))
means = [acc[0] for acc in accuracies]
stds = [acc[1] for acc in accuracies]
# 精度のバープロット
bars = ax.bar(layers, means, yerr=stds, capsize=4,
color=['#4a90d9' if i == 0 else '#2ecc71' if i <= 4
else '#f39c12' if i <= 8 else '#e74c3c'
for i in layers],
edgecolor='white', linewidth=0.5, alpha=0.85)
# 層のグループを背景色で区別
ax.axvspan(-0.5, 0.5, alpha=0.08, color='blue', label='Embedding')
ax.axvspan(0.5, 4.5, alpha=0.08, color='green', label='Low layers (1-4)')
ax.axvspan(4.5, 8.5, alpha=0.08, color='orange', label='Middle layers (5-8)')
ax.axvspan(8.5, 12.5, alpha=0.08, color='red', label='High layers (9-12)')
ax.set_xlabel("Layer", fontsize=13)
ax.set_ylabel("POS Probing Accuracy", fontsize=13)
ax.set_title("POS Tagging Probing Accuracy by Layer (BERT-base)", fontsize=14)
ax.set_xticks(layers)
ax.set_xticklabels(["Emb"] + [str(i) for i in range(1, 13)])
ax.legend(loc='lower right', fontsize=10)
ax.set_ylim(0, 1.05)
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig("pos_probing_by_layer.png", dpi=150, bbox_inches='tight')
plt.show()
このグラフでは、横軸が BERT の層(Embedding 層 + 12層の Transformer 層)、縦軸が POS タグ付けの Probing 精度を表しています。背景色で層のグループ(低層・中間層・高層)を区別しています。典型的な結果パターンとして、埋め込み層よりも低層(第1〜4層)で精度が向上し、中間層以降ではほぼ横ばいか微減する傾向が期待されます。これは品詞情報が低層で急速に符号化され、高層ではより抽象的な情報に置き換わっていくことを反映しています。サンプルサイズが小さいため分散が大きいですが、層間の相対的な傾向は大規模データでの研究結果と定性的に一致するはずです。
Control Task の実装
先述の Selectivity を計算するために、Control Task を実装します。
def create_control_labels(sentences, label_encoder):
"""
各単語型にランダムな品詞ラベルを割り当てる(Control Task)。
言語的な意味を持たないが、ラベル分布は元と同じ。
"""
np.random.seed(42)
# 全単語型を収集
word_types = set()
for sentence in sentences:
for word in sentence.split():
word_types.add(word.lower())
# 各単語型にランダムなラベルを割り当て
n_classes = len(label_encoder.classes_)
word_to_random_label = {}
for word in word_types:
word_to_random_label[word] = np.random.randint(0, n_classes)
# 全データのControl Taskラベルを生成
control_labels = []
for sentence in sentences:
for word in sentence.split():
control_labels.append(word_to_random_label[word.lower()])
return np.array(control_labels)
control_y = create_control_labels(sentences, label_encoder)
print("=== Control Task Probing (Linear Probe) ===\n")
control_accuracies = probe_all_layers(layer_features, control_y)
Control Task では、各単語に対して元の品詞ラベルの代わりにランダムなラベルが割り当てられます。np.random.seed(42) で再現性を確保し、各単語型に対して決定的にランダムラベルを割り当てることで、同じ単語は常に同じ(ランダムな)ラベルを持ちます。この設計により、Control Task は元のタスクと入力・ラベル数は同じですが、言語的な規則性を一切持ちません。
Selectivity の計算と可視化
言語タスクと Control Task の精度を比較し、Selectivity を算出します。
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
layers = list(range(13))
ling_means = [acc[0] for acc in accuracies]
ctrl_means = [acc[0] for acc in control_accuracies]
selectivity = [l - c for l, c in zip(ling_means, ctrl_means)]
# --- 左: 両タスクの精度比較 ---
ax = axes[0]
x = np.arange(13)
width = 0.35
ax.bar(x - width/2, ling_means, width, label='Linguistic (POS)',
color='#2ecc71', alpha=0.85, edgecolor='white')
ax.bar(x + width/2, ctrl_means, width, label='Control (Random)',
color='#e74c3c', alpha=0.85, edgecolor='white')
ax.set_xlabel("Layer", fontsize=13)
ax.set_ylabel("Accuracy", fontsize=13)
ax.set_title("Linguistic vs Control Task Accuracy", fontsize=14)
ax.set_xticks(x)
ax.set_xticklabels(["Emb"] + [str(i) for i in range(1, 13)])
ax.legend(fontsize=11)
ax.set_ylim(0, 1.05)
ax.grid(axis='y', alpha=0.3)
# --- 右: Selectivity ---
ax = axes[1]
colors = ['#3498db' if s > 0 else '#e74c3c' for s in selectivity]
ax.bar(layers, selectivity, color=colors, alpha=0.85, edgecolor='white')
ax.axhline(y=0, color='gray', linestyle='--', linewidth=1)
ax.set_xlabel("Layer", fontsize=13)
ax.set_ylabel("Selectivity", fontsize=13)
ax.set_title("Selectivity (Linguistic - Control)", fontsize=14)
ax.set_xticks(layers)
ax.set_xticklabels(["Emb"] + [str(i) for i in range(1, 13)])
ax.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig("selectivity_analysis.png", dpi=150, bbox_inches='tight')
plt.show()
# 数値で確認
print("\n=== Selectivity by Layer ===\n")
for layer in range(13):
layer_name = "Embedding" if layer == 0 else f"Layer {layer:2d}"
print(f"{layer_name}: Linguistic={ling_means[layer]:.3f}, "
f"Control={ctrl_means[layer]:.3f}, "
f"Selectivity={selectivity[layer]:.3f}")
左のグラフでは、緑色のバーが言語タスク(POS タグ付け)の精度、赤色のバーが Control Task の精度を示しています。右のグラフは両者の差分である Selectivity です。重要なのは絶対的な精度ではなく、言語タスクと Control Task の差です。Selectivity が正の値を持つ層では、probe が BERT の表現に含まれる言語的な構造を利用していると解釈できます。Control Task でも一定の精度が出るのは、高次元空間でのランダムな表現がある程度の線形分離可能性を持つためです。このベースラインを差し引くことで、真に言語的な情報に基づく精度を推定できるのが Selectivity の強みです。
構文的 Probing の簡易実装
最後に、Hewitt & Manning の Structural Probe の簡略版を実装します。2つの単語間の構文木上の距離を、BERT の表現から予測します。
from sklearn.linear_model import Ridge
def compute_tree_distances(sentence):
"""
簡易的に依存構造の距離を推定する。
実際の研究ではStanford Parser等の正確な構文木を使用するが、
ここではデモとして隣接単語間の距離=1として近似する。
"""
words = sentence.split()
n = len(words)
# 隣接単語は距離1、2単語離れたら距離2...(線形チェーンで近似)
distances = np.zeros((n, n))
for i in range(n):
for j in range(n):
distances[i, j] = abs(i - j)
return distances
def structural_probe_demo(sentences, layer=6):
"""
Structural Probeの簡易実装。
線形変換Bを学習し、変換後の距離が構文距離を近似するかを確認する。
"""
X_pairs = []
y_distances = []
for sentence in sentences:
all_layers, tokens = extract_hidden_states(sentence)
words = sentence.split()
n_words = len(words)
# サブワードの先頭トークンのインデックスを取得
word_indices = []
word_idx = 0
for i, token in enumerate(tokens):
if token in ["[CLS]", "[SEP]", "[PAD]"]:
continue
if not token.startswith("##"):
word_indices.append(i)
word_idx += 1
tree_dist = compute_tree_distances(sentence)
# 全単語ペアから特徴量と距離ラベルを作成
for i in range(min(n_words, len(word_indices))):
for j in range(i + 1, min(n_words, len(word_indices))):
h_i = all_layers[layer, word_indices[i]]
h_j = all_layers[layer, word_indices[j]]
diff = h_i - h_j
X_pairs.append(diff)
y_distances.append(tree_dist[i, j])
X_pairs = np.array(X_pairs)
y_distances = np.array(y_distances)
# 差分ベクトルから距離を予測する回帰モデル
# |Bh_i - Bh_j|^2 = (h_i - h_j)^T B^T B (h_i - h_j)
# これを diff^T M diff = y として、Mの要素を学習する代わりに
# 簡易的に ||diff||^2 の特徴量で回帰する
X_features = X_pairs ** 2 # 各次元の二乗を特徴量として使用
model = Ridge(alpha=1.0)
scores = cross_val_score(model, X_features, y_distances,
cv=5, scoring='r2')
return scores.mean(), scores.std()
print("=== Structural Probe (Simplified) ===\n")
print("構文距離の予測性能 (R^2 score) by layer:\n")
structural_scores = []
for layer in range(13):
r2_mean, r2_std = structural_probe_demo(sentences, layer=layer)
structural_scores.append((r2_mean, r2_std))
layer_name = "Embedding" if layer == 0 else f"Layer {layer:2d}"
print(f"{layer_name}: R^2 = {r2_mean:.3f} (+/- {r2_std:.3f})")
この実装は Structural Probe の完全な再現ではなく、概念的なデモンストレーションです。本来の Structural Probe では正確な構文木(Stanford Parser 等で生成)を使い、行列 $\bm{B}$ を勾配降下で学習しますが、ここでは構文木を線形チェーンで近似し、Ridge 回帰で距離の予測性能を評価しています。R^2 スコアが高い層ほど、その層の表現から構文的な距離をよく予測できることを意味します。
fig, ax = plt.subplots(figsize=(10, 6))
layers = list(range(13))
r2_means = [s[0] for s in structural_scores]
r2_stds = [s[1] for s in structural_scores]
ax.plot(layers, r2_means, 'o-', color='#8e44ad', linewidth=2,
markersize=8, label='Structural Probe R²')
ax.fill_between(layers,
[m - s for m, s in zip(r2_means, r2_stds)],
[m + s for m, s in zip(r2_means, r2_stds)],
alpha=0.2, color='#8e44ad')
# 中間層の範囲をハイライト
ax.axvspan(4.5, 8.5, alpha=0.1, color='orange', label='Middle layers')
ax.set_xlabel("Layer", fontsize=13)
ax.set_ylabel("R² Score", fontsize=13)
ax.set_title("Structural Probe Performance by Layer (BERT-base)", fontsize=14)
ax.set_xticks(layers)
ax.set_xticklabels(["Emb"] + [str(i) for i in range(1, 13)])
ax.legend(fontsize=11)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("structural_probe_by_layer.png", dpi=150, bbox_inches='tight')
plt.show()
このグラフでは、構文距離予測の R^2 スコアを層ごとにプロットしています。紫色の線が各層の R^2 スコア、薄い紫の帯が標準偏差の範囲です。オレンジ色のハイライトは中間層(第5〜8層)を示しています。Hewitt & Manning の研究結果に基づけば、中間層で R^2 が最も高くなることが期待されます。これは構文的な構造情報が中間層に集中しているためです。線形チェーンでの近似のため絶対値の精度は本来の Structural Probe より低くなりますが、層間の相対的な傾向は同様のパターンを示すはずです。
多義語の文脈依存表現の可視化
最後に、BERT が多義語の文脈に応じて異なる表現を生成するかを確認します。これは意味的 Probing の一種です。
from sklearn.decomposition import PCA
def visualize_word_senses(target_word, sentence_pairs, layer=8):
"""
同じ単語が異なる文脈で異なる表現を持つかをPCAで可視化する。
sentence_pairs: [(文, 語義ラベル), ...] のリスト
"""
representations = []
sense_labels = []
context_texts = []
for sentence, sense in sentence_pairs:
all_layers, tokens = extract_hidden_states(sentence)
# 対象単語のトークンインデックスを見つける
for i, token in enumerate(tokens):
if token == target_word.lower() or token == target_word:
representations.append(all_layers[layer, i])
sense_labels.append(sense)
context_texts.append(sentence)
break
if len(representations) < 2:
print(f"Warning: '{target_word}' が十分に見つかりませんでした")
return
representations = np.array(representations)
# PCAで2次元に射影
pca = PCA(n_components=2)
coords = pca.fit_transform(representations)
# 可視化
fig, ax = plt.subplots(figsize=(10, 7))
unique_senses = list(set(sense_labels))
colors = ['#2ecc71', '#e74c3c', '#3498db', '#f39c12']
for idx, sense in enumerate(unique_senses):
mask = [s == sense for s in sense_labels]
points = coords[mask]
ax.scatter(points[:, 0], points[:, 1],
c=colors[idx % len(colors)],
s=120, label=sense, alpha=0.8, edgecolors='white',
linewidth=1.5, zorder=5)
# 各点にコンテキストのラベルを付ける
for i, (x, y) in enumerate(coords):
short_ctx = context_texts[i][:40] + "..."
ax.annotate(short_ctx, (x, y), fontsize=7, alpha=0.7,
xytext=(5, 5), textcoords='offset points')
ax.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]:.1%})", fontsize=12)
ax.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]:.1%})", fontsize=12)
ax.set_title(f"Word Sense Separation: '{target_word}' (Layer {layer})", fontsize=14)
ax.legend(fontsize=11, loc='best')
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(f"word_sense_{target_word}_layer{layer}.png", dpi=150,
bbox_inches='tight')
plt.show()
print(f"\n説明分散比: PC1={pca.explained_variance_ratio_[0]:.3f}, "
f"PC2={pca.explained_variance_ratio_[1]:.3f}")
# "bank" の2つの語義
bank_sentences = [
("I went to the bank to deposit money", "Financial"),
("The bank approved my loan application", "Financial"),
("She works at a large investment bank", "Financial"),
("The bank offers good interest rates", "Financial"),
("We sat on the bank of the river", "River"),
("The boat was tied to the river bank", "River"),
("Flowers grew along the bank of the stream", "River"),
("The fisherman stood on the muddy bank", "River"),
]
print("=== 多義語の文脈依存表現の可視化 ===\n")
visualize_word_senses("bank", bank_sentences, layer=8)
このグラフは、”bank” という同一単語の表現ベクトルを PCA で2次元に射影したものです。緑色の点が金融機関としての “bank”、赤色の点が川岸としての “bank” を表しています。BERT の第8層(高めの中間層)では、同じ “bank” でも文脈に応じて異なる位置にマッピングされ、2つの語義クラスタが分離されることが期待されます。PC1 と PC2 の説明分散比は、この2次元射影でどの程度の情報が保持されているかを示します。静的な埋め込み(Word2Vec)では “bank” は1つの固定ベクトルにしかなりませんが、BERT の文脈依存表現は語義を区別できる表現力を持っていることが、この可視化で直感的に確認できます。
まとめ
本記事では、BERTology の中核手法である Probing タスクについて、理論的な背景から Python での実装まで解説しました。
- Probing タスクの基本設計: 事前学習モデルの内部表現を固定し、簡単な分類器(線形 probe)で言語知識の存在を検出する。probe を簡単にすることで、表現そのものに情報が含まれているかを正しくテストできます
- 層ごとの情報分布: BERT は低層で形態論的情報(品詞)、中間層で構文的情報(依存構造、句構造)、高層で意味的情報(意味役割、共参照)を処理する階層構造を持ち、古典的な NLP パイプラインを内部的に「再発見」しています
- 構文的 Probing(Structural Probe): Hewitt & Manning の手法により、BERT の中間層の表現空間に構文木の距離構造が線形変換でアクセス可能な形で埋め込まれていることが示されました
- 意味的 Probing: 語義曖昧性解消、意味役割ラベリング、共参照解析、さらには事実的な世界知識まで、BERT の表現に含まれる多様な意味情報が Probing で検出されています
- Control Tasks と Selectivity: probe の精度を正しく解釈するには、言語的な意味を持たない Control Task との差分(Selectivity)を確認することが不可欠です。これにより、表現に真に言語知識が含まれているかと、probe の記憶能力を区別できます
- ヘッドの専門化と冗長性: BERT の注意ヘッドの多くは機能的に冗長であり、少数のヘッドが言語的に重要な情報を担っています。この知見はモデルの圧縮や効率化に直接活用できます
BERTology と Probing の手法は、BERT に限らず GPT シリーズ、T5、RoBERTa など多くの事前学習モデルに適用されています。モデルが巨大化・複雑化する現在、その内部を理解するための体系的な方法論はますます重要になっています。
次のステップとして、以下の記事も参考にしてください。