AIエージェント入門

jacobian-lensを動かす、AIエージェント評価への組み込み方

jacobian-lensを動かす、AIエージェント評価への組み込み方

この記事の結論

Anthropicが公開したOSS jacobian-lens(Apache-2.0)を実際にインストールして動かし、何が分かり何が分からないかをコードで検証。エージェント評価ループへの組み込み案も解説する。

動作環境:macOS 15 / Python 3.11.15 / PyTorch 2.13.0(CPU版、GPUなし)/ transformers 5.14.1

Anthropicが2026年7月2日にGitHubで公開したOSS「jacobian-lens」(リポジトリ名 anthropics/jacobian-lens、Apache-2.0)は、モデルの内部を覗いて「まだ言葉になっていないが、言おうとしていること」を数値で取り出すツールだ。研究発表としては「AIの心が読めた」的な話題になりがちだが、実際にコードを書いて動かしてみると、もっと地味で実務的な代物だとわかる。

この記事では実際に手元の環境でリポジトリをクローンし、pip install -e .してモデルとレンズをロードし、公式の比較コードを走らせた。以下はそのログと、動かして初めてわかった制約、そしてAIエージェントの評価パイプラインに組み込むとしたらどうなるかの実装アイデアだ。数字やコードはすべて実行結果か公式リポジトリ・Hugging Face・Neuronpediaの一次情報から取っている。経営・AI監査の観点は姉妹メディアUravationの記事に譲り、ここでは開発者として「動かす」角度に絞る。

jacobian-lensは何を計算しているのか

公式README・論文(“Verbalizable Representations Form a Global Workspace in Language Models”)の説明を要約すると、jacobian-lens(パッケージ名 jlens)は次の式で表される「輸送写像」を各層ごとに1つ学習する。

lens_l(h) = unembed( J_l @ h )
J_l = E[ ∂h_final / ∂h_l ]

ある層 l の残差ストリームベクトル h を、最終層の基底に「線形輸送」してから、モデル自身のunembedding行列でボキャブラリのランキングに変換する。J_l はプロンプト・位置についての期待値として1回だけ学習され、以降はどのプロンプトにも使い回せる「その層専用の変換行列」になる。従来からある「logit lens」(中間層の値をそのまま最終層のunembeddingに突っ込むだけの手法)との違いは、この J_l による線形変換が入る点だ。単純な射影よりも、その層が最終的に「言おうとしている」語彙を高精度で拾えるとAnthropicは主張している。

ライセンスは2階建てになっている点に注意したい。GitHub上のコード本体(anthropics/jacobian-lens)はApache-2.0だが、Hugging Face上で配布されている事前学習済みレンズファイル一式(neuronpedia/jacobian-lens)はMITライセンスで公開されている。コードと学習済み重みでライセンスが異なるので、再配布や商用利用の際はどちらを使っているか区別して確認する必要がある。

5分で動かす——インストールと最初の1回

まずはインストール。pyproject.tomlを見る限り、パッケージ名はjlens(バージョン0.1.0)、Python要件は3.10以上、依存関係はtorchhuggingface_hubtransformers>=5.5numpyの4つだけ。CLIコマンドは用意されていない[project.scripts]の定義がなく、import jlensで読み込むPythonライブラリとして設計されている。

# Python 3.10 以上が必要(手元では3.11.15で動作確認)
git clone https://github.com/anthropics/jacobian-lens.git
cd jacobian-lens
pip install -e .

# CLIはない。ライブラリとして読み込めるか確認するだけ
python -c "import jlens; print(jlens.__file__)"

GPU専用というわけではない。README・walkthrough.ipynbのサンプルコードは.cuda()を前提に書かれているが、実際にGPUなしのMacBook(PyTorch CPU版)でも、小型モデルであれば問題なく動いた。ただし公式に「CPUで動作保証」とは書かれていないので、大きいモデルで試す場合は現実的な速度が出るか自分で確認したほうがいい。

項目 内容
パッケージ名 jlens(v0.1.0)
コードのライセンス Apache-2.0(GitHub anthropics/jacobian-lens
事前学習済みレンズのライセンス MIT(Hugging Face neuronpedia/jacobian-lens
Python要件 3.10以上(動作確認:3.11.15)
主な依存関係 torch, huggingface_hub, transformers>=5.5, numpy
CLIツール なし(Pythonライブラリとしてimport jlensで使用)
GitHubスター数/フォーク数 1,582 / 230(2026年7月27日時点、GitHub API調べ)
リポジトリ作成日 2026年7月2日(GitHub API調べ)
保守方針 「Reference implementation. Not maintained and not accepting contributions.」(README記載)

実際に動かしてみた——ログそのまま公開する

手元にGPUがないので、Hugging Face上で事前学習済みレンズが配布されている中でも最小サイズのgpt2-small(124Mパラメータ、OpenAI GPT-2、レンズファイルは12.98MB)で試した。走らせたのは公式walkthrough.ipynbとほぼ同じ、多段推論(multi-hop reasoning)のプロンプトだ。

import torch, transformers, jlens

MODEL_NAME = "openai-community/gpt2"
hf_model = transformers.AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=torch.float32)
tokenizer = transformers.AutoTokenizer.from_pretrained(MODEL_NAME)
model = jlens.from_hf(hf_model, tokenizer)
# -> HFLensModel(GPT2LMHeadModel, n_layers=12, d_model=768)

lens = jlens.JacobianLens.from_pretrained(
    "neuronpedia/jacobian-lens",
    filename="gpt2-small/jlens/Salesforce-wikitext/gpt2_jacobian_lens.pt",
)
# -> JacobianLens(d_model=768, n_prompts=277, source_layers=[0..10] (11 layers))

prompt = "Fact: The currency used in the country shaped like a boot is"
layers = [3, 6, 9, 10]

jlens_logits, model_logits, _ = lens.apply(model, prompt, layers=layers, positions=[-2])
logit_lens, _, _ = lens.apply(model, prompt, layers=layers, positions=[-2], use_jacobian=False)

def top5(logits):
    return [tokenizer.decode([t]) for t in logits.topk(5).indices]

for layer in layers:
    print(f"L{layer:>2} logit-lens: {top5(logit_lens[layer][0])}")
    print(f"L{layer:>2} J-lens:     {top5(jlens_logits[layer][0])}")
print("model final:", top5(model_logits[0]))

初回実行はモデルの重みダウンロードを含めてロードに114秒かかったが、ローカルキャッシュが効く2回目以降はモデルロードが2.7秒、レンズロードが数秒に短縮された。apply()自体の計算はCPUでも0.1秒程度。実際の出力はこうなった。

L 3 logit-lens: ['strap', 'stra', 'loader', 'legged', 'y']
L 3 J-lens:     [' boot', 'boot', 'Boot', ' Boot', ' boots']
L 6 logit-lens: ['strap', 'stra', 'legged', 'y', 'leg']
L 6 J-lens:     ['boot', ' boot', 'Boot', ' Boot', ' boots']
L 9 logit-lens: ['strap', 'stra', 'legged', 'y', 'leg']
L 9 J-lens:     ['strap', 'loader', 'boot', 'legged', ' boot']
L10 logit-lens: ['strap', 'stra', 'leg', 'y', 'legged']
L10 J-lens:     ['strap', 'stra', 'legged', 'loader', 'leg']
model final:    ['strap', '.', 'leg', ',', 'stra']

正直に書くと、gpt2-smallは「ブーツの形をした国の通貨は?」という多段推論に正解できず、最終出力は「Italy」でも「lira」でもなくstrap(”bootstrap”の連想と思われる)だった。それでも面白いのは、レイヤー3・6の時点でJ-lensだけがはっきり「boot」系のトークンを浮かび上がらせている点だ。同じ層で普通のlogit-lensを使うと、まだstrap系の表層的な続き方しか見えない。つまりJ-lensは、モデルが最終的に何を言うかとは別に、「途中の層で何に反応しているか」をより早く・より明確に取り出せている。ただしgpt2-small自体が力不足なため、その反応は「Italy」という概念ではなく「boot→bootstrap/bootleg」的な表層の語連想に留まっていた。小さいモデルで試すと、拾えるのは概念というより語彙的な連想止まりになる——これは自分で動かして初めて実感できた限界だ。

もう1つ、モデルのサイズとレンズファイルのサイズの関係も実際に確認した。Hugging Face neuronpedia/jacobian-lensには2026年7月27日時点で38個のモデル向けレンズが公開されており、サイズはモデルの大きさに比例して大きく変わる。

モデル パラメータ規模の目安 レンズファイルサイズ(実測)
gpt2-small 1.24億 12.98MB
gemma-3-270m 2.7億 13.93MB
Qwen3.5-4B 40億 406.33MB
Llama-3.3-70B-Instruct 700億 約10.60GB

レンズは層数×d_model²に比例したサイズになるため、大きいモデルを何個もローカルにキャッシュするとディスクをかなり圧迫する。使うモデルを絞って、必要な分だけダウンロードするほうがいい。

レンズはどうやって学習されるのか——fit()の中身

事前学習済みレンズを使うだけならモデルを学習し直す必要はないが、「どう作られているか」を知っておくと、限界も理解しやすい。README・jlens/fitting.pyによれば、jlens.fit()は汎用のWebテキストコーパスからサンプリングしたプロンプト集合に対して、各プロンプト・各位置ごとにJ_l(層lのヤコビアン)を計算し、その移動平均を取るだけのシンプルな処理だ。

from jlens.examples import load_wikitext_prompts
import jlens

prompts = load_wikitext_prompts(n_prompts=100)
lens = jlens.fit(model, prompts, dim_batch=32, max_seq_len=128, checkpoint_path="ckpt.pt")
lens.save("jacobian_lens.pt")
# README: "Quality saturates quickly (§9.3); ~100 prompts is usable."

# 複数マシンに分割して学習した場合はJacobianLens.merge()で結合できる
merged = jlens.JacobianLens.merge([lens_a, lens_b, lens_c])

実際、Neuronpediaがgpt2-small用に配布しているレンズのconfig.yamlには学習の実データが残っていた。目標はSalesforce/wikitext(wikitext-103-raw-v1)から1,000プロンプトだったが、収束判定(stop_at_delta=0.002)により277プロンプトで早期終了している。これは筆者が実際にロードしたレンズオブジェクトのn_prompts=277という値とそのまま一致していた——公開されている学習ログと、手元で読み込んだレンズの中身が矛盾なく一致していることを確認できたことになる。学習にはNVIDIA B200 GPU 1枚(bfloat16)が使われていた。ポイントは、この学習はモデルごとに1回だけ行えばよく、その後のapply()は既に見た通りCPUでも一瞬で終わる点だ。

Neuronpediaのデモで触れること・触れないこと

Anthropicは研究ブログ(2026年7月6日付「A global workspace in language models」)で、「コードリポジトリの公開に加えて、Neuronpediaと提携しオープンウェイトモデルでのインタラクティブなデモを提供する」と発表している。デモ本体はneuronpedia.org/jlens、モデル別ページはneuronpedia.org/[モデルID]/jlensという形式で、たとえばQwen3.6-27B用ページにプロンプトを送ると、ブラウザ上でJ-space(J-lensが可視化する内部状態)を確認できる。

Neuronpedia公式ブログによれば、公開時点から「Gemma・Llama・GPT-OSS・Qwenファミリーからさらに10モデルを追加した」とあり、Hugging Face上では合計36〜38モデル分の事前学習済みレンズが配布されている(本記事執筆時点でHugging Face APIから直接確認した実際のディレクトリ数は38)。重要なのは、デモが動くのはあらかじめレンズが学習済みの公開モデルだけという点だ。自社でファインチューニングした独自モデルをブラウザ上で解析する機能はない。自分のモデルで試すには、前節のjlens.fit()で自前のレンズを学習する必要がある。

もう1つ確認できたのは、リポジトリに同梱されているjlens.examples.EXAMPLESという8種類の組み込みプロンプト集だ。これは「Multi-hop reasoning(多段推論)」「Voluntary modulation(意図的な思考誘導)」「Bug in code(コードのバグ)」「Agentic Misalignment(エージェントの逸脱行動)」といったセクション名を持ち、実際にpython -cでリスト表示すると次の8件が確認できた。

multihop               | Multi-hop reasoning
modulation-topic        | Voluntary modulation: topic
modulation-arithmetic   | Voluntary modulation: arithmetic
ascii-face              | ASCII face
off-by-one              | Bug in code
overdose-flag           | Overdose flag
greatest-fear           | Greatest fear (don't say it)
blackmail               | Agentic Misalignment (blackmail honeypot)

「Bug in code」や「Agentic Misalignment」のような項目が最初から用意されていることからも、この手法が単なる言語学的な興味本位のデモではなく、エージェントの安全性評価を意識して作られていることがうかがえる。

エージェントの評価ループにどう組み込むか

ここからは、実際に手を動かして得た理解をもとにした「実装アイデア」であり、Anthropicやjacobian-lens自体が公式に推奨している運用ではない点を先に断っておく。README には「Reference implementation. Not maintained and not accepting contributions.」と明記されており、研究段階のツールとして扱うべきだ。そのうえで、以下のような組み込み方は現実的だと考えている。

アイデア:固定プロンプト集合に対する「内部状態の回帰テスト」

通常のエージェント評価は、最終的な出力テキストや行動ログを見て合否判定する。J-lensを使うと、これに加えて「モデルの中間層が、変更の前後でどのトークンに反応しているか」を数値で比較できる。jlens.examples.EXAMPLES(あるいは自作の評価プロンプト集)を固定のプローブセットとして、モデル更新やシステムプロンプト変更のたびに中間層の反応をスナップショットして差分を取る、というイメージだ。

# 疑似コード(jlensの実装は既出の apply() をそのまま使う想定)
from jlens.examples import EXAMPLES, resolve_prompt

PROBE_LAYERS = [model.n_layers // 3, model.n_layers // 2]

def snapshot(model, lens, tokenizer):
    result = {}
    for ex in EXAMPLES:  # multihop, off-by-one, blackmail など8種
        prompt = resolve_prompt(ex, tokenizer)
        logits, _, _ = lens.apply(model, prompt, layers=PROBE_LAYERS, positions=[-1])
        result[ex.slug] = {
            l: tokenizer.decode([logits[l][0].argmax()]) for l in PROBE_LAYERS
        }
    return result

baseline = snapshot(model, lens, tokenizer)
# ここでモデルの重み・システムプロンプト・ガードレール設定などを変更する
current = snapshot(model_after_change, lens, tokenizer)

diff = {slug: (baseline[slug], current[slug])
        for slug in baseline if baseline[slug] != current[slug]}
# diffに出たプロンプトを「内部の反応が変わった箇所」として人手レビューに回す

この方式には明確な限界がある。Anthropic自身が研究発表で「J-lensは間違いなく不完全な手法であり、モデルの”真の作業空間”を近似的にしか捉えられない。たとえば単一トークンに対応する概念しか識別できない」と述べている通り、複数トークンにまたがる複雑な概念や、トークン化されにくい抽象的な意図は原理的に拾えない。あくまで既存の出力ベースの評価(レッドチーミングやガードレールのテスト)を補完するもう1つの信号源として位置づけるのが妥当だ。実装のコストは小さい——apply()自体は本記事の検証でも0.1秒程度で終わっており、CIのナイトリージョブに組み込むハードルは高くない。

【要注意】使ってみて分かった限界とハマりどころ

1. GPU前提のサンプルコードに惑わされる

❌ walkthrough.ipynbのコードをそのまま動かないと思い込む

.cuda()を外してCPUで実行すれば、小型モデルでは問題なく動く(本記事はCPUのみのMacBookで実行し、ログをそのまま掲載した)。ただし大きいモデルではCPUだと非現実的な時間がかかる可能性があり、公式に最小動作要件が明記されているわけではない。

2. baseモデルにchat形式のexampleを渡すとエラーになる

jlens.examplesの「off-by-one(Bug in code)」のようなchat形式のプロンプトを、gpt2のようなbase(非instruct)モデルにそのまま使う

⭕ 実際に試したところValueError: Cannot use chat template functions because tokenizer.chat_template is not set...というエラーが出た。chat形式のexampleは、対応するchat_templateを持つinstructモデル(例:gemma-3-4b-itなど)と、そのモデル用のレンズを組み合わせて使う必要がある。

3. ライセンスの二重構造を見落とす

❌ 「Apache-2.0だから何でも自由に再配布できる」と思い込む

⭕ コード本体(GitHub)はApache-2.0だが、Hugging Face上の事前学習済みレンズ配布物(neuronpedia/jacobian-lens)はMITライセンス。再配布・商用利用の際はどちらを使っているか区別して確認する。

4. 大きいモデルのレンズを何個もダウンロードしてディスクを圧迫する

❌ 全38モデル分のレンズを一括ダウンロードする

⭕ 70Bクラスのモデルではレンズファイルだけで約10.6GBある(実測)。自分が実際に使うモデルの分だけダウンロードする。

5. 「保守されているOSS」だと思って本番依存にする

❌ プロダクションの継続的な依存先としてそのまま組み込む

⭕ READMEに「Reference implementation. Not maintained and not accepting contributions.」と明記されている。研究用の参照実装として扱い、本番導入する場合は自チームでフォークして保守する前提を持つ。

経営・AI監査の観点は姉妹記事へ

本記事はあくまで開発者としてコードを動かす角度に絞った。同じjacobian-lensについて、AIガバナンス・内部監査での位置づけや、企業がこの種の解釈可能性研究をどう扱うべきかという経営視点の整理は、姉妹メディアUravationの記事「Anthropic「J-Lens」で変わるAI監査とガバナンスの現在地」で解説している。あわせて読むと、技術と経営の両面から今回の発表を理解できるはずだ。

よくある質問

Q. jacobian-lensは商用利用できますか?

コード本体はApache-2.0ライセンスで商用利用・改変・再配布が可能です。ただしHugging Face上で配布されている事前学習済みレンズファイルはMITライセンスと別立てで提供されているため、どちらを使っているか区別して確認してください。

Q. GPUがないと動きませんか?

公式サンプルコードは.cuda()呼び出しを前提にしていますが、本記事ではCPUのみの環境でも小型モデル(gpt2-small)で実際に動作しました。ただし公式に最小動作要件が明記されているわけではなく、大きなモデルでは現実的な速度が出ない可能性があります。

Q. コマンドラインツール(CLI)は用意されていますか?

用意されていません。パッケージ設定ファイル(pyproject.toml)にCLIコマンドの定義はなく、Pythonからimport jlensして使うライブラリとして設計されています。

Q. Neuronpediaのデモで自分の独自モデルを解析できますか?

できません。デモはGemma・Llama・GPT-OSS・Qwenなど、あらかじめレンズが学習済みの公開モデル専用です。独自にファインチューニングしたモデルを解析したい場合は、jlens.fit()で自分のモデル用のレンズを別途学習する必要があります。

参考・出典

まとめ:今日から始める3つのアクション

  1. 今日git cloneしてpip install -e .し、gpt2-smallのような小型モデル+Hugging Face上の対応レンズで、本記事のコードをそのまま動かしてみる(GPU不要)。
  2. 今週中:自チームのエージェントで使っているベースモデルに対応する事前学習済みレンズがneuronpedia/jacobian-lensにあるか確認する。
  3. 今月中:既存のレッドチーミング・ガードレール評価に加えて、jlens.examples相当の固定プロンプト集に対する内部状態スナップショットを取り、モデル更新前後の差分を見る運用を小さく試してみる。

あわせて読みたい


著者:佐藤傑(さとう・すぐる)
株式会社Uravation代表取締役。X(@SuguruKun_ai)フォロワー10万人超。100社以上の企業向けAI研修・導入支援を展開。著書累計3万部突破。

この記事を読んでAIエージェントの評価・ガバナンス体制を見直したくなった方へ

UravationではAIエージェント導入の研修・コンサルを行っています。

Need help moving from reading to rollout?

この記事を読んで導入イメージが固まってきた方へ

Uravationでは、AIエージェントの要件整理、PoC設計、社内導入、研修まで一気通貫で支援しています。

この記事をシェア

X Facebook LINE

※ 本記事の情報は2026年7月時点のものです。サービスの料金・仕様は変更される可能性があります。最新情報は各サービスの公式サイトをご確認ください。

関連記事