「『りんご』のベクトルってどんな値なんだろう?」という素朴な好奇心から始めて、最終的に embedding 空間に "果物軸" を見つけて新規単語を「果物っぽさスコア」でランキングするまでやってみた記録。
OpenAI の text-embedding-3-large は 1単語を 3072次元 の数値ベクトルに変換する。
この数字の塊が何を捉えているのか、curl と Python 標準ライブラリだけで実際に解剖してみた。
上の散布図は本記事の実験結果を PCA で 2次元に落としたもの。横軸 PC1 が 「果物極 ⇔ Apple企業極」 の意味勾配になっていて、オレンジの りんご → 林檎 → アップル が表記の違いだけで両極の間をなめらかに移動していくのが見える。再現コード一式は GitHub: masafykun/embedding-explore に置いた。
TL;DR
- 「りんご」は
text-embedding-3-smallで1536次元、text-embedding-3-largeで3072次元のベクトルに変換される(どちらもL2正規化済み) - 「りんご」⇔「みかん」のコサイン類似度は 0.5695(同カテゴリにしては意外と低い)
-
Appleとappleの類似度は 0.87 だが、Apple⇔りんごは 0.47 — 大文字小文字だけで意味(企業 vs 果物)が分離されている - 「特定の次元 N が果物概念を捉えている?」は 間違い。果物概念は3072次元に薄く広く分散して埋め込まれている(top10次元集めても全体の5.3%しかカバーできない)
- ただし「果物方向ベクトル」は 作れる。これに射影すれば「果物っぽさスコア」が連続値で出せる(線形プロービング / コンセプトベクトルの基本形)
- 結果: スイカ > メロン > トマト > キャベツ > ナス という直感に沿った「果物グラデーション」が観察できる
環境
# OpenAI API キーが手元にあればOK。Python標準ライブラリのみ使用。
export OPENAI_API_KEY=sk-...
python3 --version # 3.8 以降ならOK
1. 「りんご」のベクトルを取得する
まずは1単語、text-embedding-3-small (1536次元) で取得。
curl -s https://api.openai.com/v1/embeddings \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"text-embedding-3-small","input":"りんご"}' \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
v = d['data'][0]['embedding']
print('dim:', len(v))
print('first 10:', v[:10])
print('L2 norm:', sum(x*x for x in v)**0.5)
"
実行結果:
dim: 1536
first 10: [-0.0185, -0.0151, -0.0026, -0.0238, 0.0144, -0.0558, 0.0350, 0.0529, -0.0169, -0.0053]
L2 norm: 0.9997
L2ノルムが 1.0 にほぼ等しい。OpenAI の embedding はサーバ側で正規化済みなので、コサイン類似度 = 内積 (dot product) で計算してよい。
モデル別の次元数
| モデル | 次元数 | 備考 |
|---|---|---|
text-embedding-3-small |
1536 | 安価・高速 |
text-embedding-3-large |
3072 | OpenAI 最大、本記事で使用 |
text-embedding-ada-002 |
1536 | 旧世代 |
参考までに、業界最大級だと NVIDIA NV-Embed-v2 や E5-mistral-7b-instruct が 4096次元。ただし「次元数 = 精度」ではなく、3-large は次元削減 (256次元) しても ada-002 (1536次元) より高精度、という結果も報告されている。
2. 「りんご」と「みかん」の距離
ここから先は3-largeに切り替えて、2語を一度に取得する。
curl -s https://api.openai.com/v1/embeddings \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"text-embedding-3-large","input":["りんご","みかん"]}' \
> /tmp/fruit.json
python3 << 'PY'
import json, math
d = json.load(open('/tmp/fruit.json'))
a, b = [item['embedding'] for item in d['data']]
cos = sum(x*y for x,y in zip(a,b)) # 既に正規化済みなのでdot=cos
print(f'cos sim: {cos:.4f}')
print(f'cos dist: {1-cos:.4f}')
print(f'angle: {math.degrees(math.acos(cos)):.2f}°')
PY
結果:
cos sim: 0.5695
cos dist: 0.4305
angle: 55.28°
0.57 は、果物同士の類似度としては意外と低い。Apple vs apple (0.87) や りんご vs 林檎 (0.74) と比べると、別カテゴリの語に近い距離感だ。
| コサイン類似度 | |
|---|---|
| 全くの無関係(りんご vs 自動車) | 0.1〜0.3 |
| ゆるい関連(果物カテゴリ間) | 0.3〜0.5 |
| りんご vs みかん | 0.57 |
| 強い同義(apple vs アップル) | 0.7〜0.85 |
| ほぼ同一表現(apple vs Apple) | 0.9以上 |
embedding は「単語の意味」というより「文脈での使われ方」を捉えているため、見た目・味・色がまったく違うりんごとみかんは、同じ「果物カテゴリ」というだけでは強くは寄ってこない、ということが分かる。
3. 5語の類似度マトリックス
words = ["りんご","みかん","ぶどう","バナナ","自動車"]
# ... embeddings 取得 → 全ペアで cos 計算
| りんご | みかん | ぶどう | バナナ | 自動車 | |
|---|---|---|---|---|---|
| りんご | 1.000 | 0.570 | 0.492 | 0.443 | 0.315 |
| みかん | 0.570 | 1.000 | 0.468 | 0.454 | 0.242 |
| ぶどう | 0.492 | 0.468 | 1.000 | 0.403 | 0.302 |
| バナナ | 0.443 | 0.454 | 0.403 | 1.000 | 0.242 |
| 自動車 | 0.315 | 0.242 | 0.302 | 0.242 | 1.000 |
- 果物4語は 0.40〜0.57 のレンジで密、自動車との組だけ 0.24〜0.32 に落ちる → 明確な「カテゴリ境界」が出ている
- りんごは果物クラスタの中心(他果物への平均が一番高い)
- バナナだけ少し孤立気味 — カタカナ・南国系で、和文脈の果物群からやや遠い
4. Apple は果物か企業か?
ここで意地悪な実験。同じ "りんご" を意味する10通りの表記 を入れて、表記の違いが意味をどれだけ動かすかを見る。
words = ["りんご","林檎","アップル","Apple","apple","iPhone","Mac","果物","fruit","自動車"]
りんご視点(=純粋な果物)からの近さランキング
林檎 0.736 ████████████████████████████ ← 同義
アップル 0.660 ██████████████████████████ ← カタカナは中立
果物 0.529 █████████████████████ ← 上位カテゴリ
apple 0.521 ████████████████████ ← 小文字英語は果物寄り
fruit 0.498 ███████████████████ ← 英語の上位カテゴリ
Apple 0.475 ███████████████████ ← 大文字Aは企業寄り
iPhone 0.404 ████████████████ ← 製品名
自動車 0.315 ████████████ ← 無関係ベースライン
Mac 0.278 ███████████ ← 「りんご < Mac」逆転!?
Apple視点(=企業)からの近さランキング
apple 0.874 ████████████████████████████████ ← 同一語、大小違いだけ
アップル 0.690 ███████████████████████████ ← カタカナ
iPhone 0.674 ██████████████████████████ ← 製品エコシステム
林檎 0.594 ███████████████████████ ← 漢字でも企業寄り
Mac 0.533 █████████████████████ ← 製品
りんご 0.475 ███████████████████ ← ひらがなは果物寄り
fruit 0.469 ██████████████████ ← 英語一般語
果物 0.365 ██████████████ ← 純粋な果物
自動車 0.205 ████████ ← 無関係
この結果から分かること
-
apple⇔Appleの類似度 0.87 が全ペア中最高。表記揺れは最も強い類似度になる -
アップル(カタカナ) はりんご (0.66) と Apple (0.69) のほぼ中間 — 日本語話者の語感どおり、両方の意味を含む中立語として学習されている -
大文字小文字で意味が分裂:
Appleは iPhone(0.67) > fruit(0.47)、appleは fruit(0.57) ≒ iPhone(0.60) — モデルが capitalization から意味を推測している -
「りんご」から見ると
Mac(0.28) <自動車(0.32) の逆転 — ひらがな表記は完全に果物文脈で学習されているため、企業製品の Mac よりも、果物文脈での共起がある「自動車」(りんご狩り、果物の運搬車など?) の方が近い
embedding が辞書的な意味ではなく文脈での共起パターンを捉えていることが、この表記実験で如実に見える。
5. 「次元 N が果物軸」じゃないの?
ここで自然に湧く疑問。
果物群と非果物群を比較したとき、特定の次元 N で値が極端に変わってるなら、その N 次元が「果物っぽさ」を担当しているのでは?
実験してみる。果物6語 vs 非果物6語の各次元平均の差を取って、差が大きい次元 top10 を見る。
fruits = ["りんご","みかん","ぶどう","バナナ","いちご","もも"]
others = ["自動車","椅子","本","パソコン","鉛筆","ビル"]
# embeddings 取得後...
diff = [mean_fruits[i] - mean_others[i] for i in range(3072)]
top10 = sorted(range(3072), key=lambda i: -abs(diff[i]))[:10]
結果:
=== 差分が大きい次元 top10 ===
dim diff fruit_mean other_mean
157 -0.0536 -0.0288 +0.0248
159 -0.0516 -0.0328 +0.0188
643 -0.0494 -0.0634 -0.0140
...
=== 集中度の検証 ===
全 3072 次元の差分ベクトル ||diff||² = 0.3769
top10 次元だけの ||diff||² = 0.0200 (5.3%)
top1 次元の寄与だけ = 0.0029 (0.8%)
top1 次元 (dim=157) は果物概念のたった 0.8%、top10 集めても 5.3% しか説明できない。
dim=157 単独で見るとそれっぽいのに
果物: -0.0594 〜 -0.0155 (全部マイナス)
非果物: +0.0115 〜 +0.0392 (全部プラス)
完璧に分離できているように見える! でもこれは罠で、こういう次元が 果物以外のサンプルセットでも 22.2% の次元で出現する(全果物が同符号になる次元 = 682/3072)。ランダムなら 2⁻⁵ = 3.1% のはずなので明らかに偏ってはいるが、「ここが果物軸」と特定できる単一次元は存在しない。
なぜ単一次元では捉えられないのか
3つの理由が複合している:
- 分散表現 (distributed representation): ニューラル埋め込みでは概念が数千次元に薄く広く分散する。これは表現容量を増やすための設計
- 回転不変性: embedding 空間は学習時の初期化で任意に回転できる(距離は保たれる)。「dim=157」という軸番号自体に本質的意味はない。再学習すれば dim=2841 に移っているかもしれない
- 多義性 (polysemanticity / superposition): 仮に dim=157 が果物に反応していても、同時に「赤い物」「丸い物」「秋の季語」など複数の無関係な概念も同じ次元で表現されている可能性が高い (Anthropic の interpretability 研究で繰り返し示されている現象)
6. 「方向」なら取り出せる:コンセプトベクトル
座標軸では取れないが、特定の方向ベクトルとしてなら「果物概念」を取り出せる。これが線形プロービング / TCAV (Testing with Concept Activation Vectors) の基本アイデア。
fruit_direction = mean(fruit_vectors) - mean(non_fruit_vectors)
fruit_direction /= norm(fruit_direction) # 単位ベクトルに正規化
このベクトルとの内積が「果物っぽさスコア」になる。
学習サンプル自身でのスコア(sanity check)
学習に使った既知語
りんご +0.4089 ████████████████████████████████████
みかん +0.4930 ██████████████████████████████████████████
ぶどう +0.4013 ████████████████████████████████████
バナナ +0.3578 ███████████████████████████████
いちご +0.4624 ██████████████████████████████████████
もも +0.1885 █████████████████
─────── 0 ───────
自動車 -0.2360 ··················
椅子 -0.2334 ··················
本 -0.2174 ················
パソコン -0.2063 ················
鉛筆 -0.2459 ····················
ビル -0.2324 ··················
果物 +0.19〜+0.49、非果物 -0.21〜-0.25 で綺麗に分離されている。
未知語でのテスト
新規14語を同じ方向に射影してランキング:
=== 果物っぽさスコア ranking ===
スイカ +0.301 ████████████████████████████████████████████████████████████
メロン +0.291 ██████████████████████████████████████████████████████████
くだもの狩り +0.275 ███████████████████████████████████████████████████████
トマト +0.241 ████████████████████████████████████████████████
リンゴジュース +0.220 ████████████████████████████████████████████
玉ねぎ +0.216 ███████████████████████████████████████████
キャベツ +0.197 ███████████████████████████████████████
アップルパイ +0.182 ████████████████████████████████████
ナス +0.138 ███████████████████████████
椎名林檎 +0.132 ██████████████████████████
花 +0.130 █████████████████████████
─────── 0 ───────
バット -0.010 ·
Apple Inc. -0.010 ··
机 -0.110 ······················
この結果が面白い理由
- スイカ・メロンが「もも」より上位: 植物学的にはモモが核果でコア果物、スイカ・メロンは「果実的野菜」のはずだが、スーパーのフルーツ売場・デザート文脈での使用 がモデルに刷り込まれていて逆転している
- トマト > キャベツ > ナス という野菜のグラデーション: トマトが果物寄りなのは "tomato is a fruit?" 論争データやミニトマト=デザート文脈の効果。日常言語の感覚と一致
- 「リンゴジュース」(0.220) > 「アップルパイ」(0.182): ジュースは果物そのまま、パイは小麦・砂糖・調理という他要素が混じる、という直感そのまま
- 「Apple Inc.」がほぼ 0: 企業ドメインは果物方向と完全に直交している。同じ Apple 表記でも別概念方向に乗っている強い証拠
- 「机」だけがマイナス: 学習に使った家具・物体群と同類なので、果物方向の反対側に位置する。「バット」が0付近なのは家具とも違うスポーツ用具だから
スコア +0.20 あたりに「食物っぽさ」の閾値があり、これだけで簡易な「果物 / 食物分類器」が作れてしまう、というのも興味深い。
まとめ
- OpenAI Embeddings は L2正規化済み なので、
dot(a, b)がそのままコサイン類似度になる - 「りんご」のような単純な単語でも、表記(漢字/ひらがな/カタカナ/英語大小)によって embedding 空間内の意味的位置がはっきり分裂する
- 「特定の次元 N に概念が宿っている」という素朴な解釈はほぼ間違い。embedding は分散表現 であり、概念は多数の次元の特定の線形組み合わせ(方向ベクトル) として埋め込まれている
- 学習データ少数 (各6個) でも、平均差分によるコンセプトベクトルは驚くほどうまく機能する。新規語の概念スコアリングが線形演算1回で実現できる
- これは LLM の解釈可能性研究 (TCAV, linear probing, representation engineering) の入り口に立つ手法でもある
「embedding は意味ではなく文脈を捉えている」というよく聞くフレーズが、実験を通じて手応えのある実感に変わるはず。お試しあれ。
参考
- OpenAI Embeddings ドキュメント
- TCAV (Testing with Concept Activation Vectors) - Kim et al. 2018
- Anthropic, "Toy Models of Superposition" - Elhage et al. 2022
- 再現用コード: https://github.com/masafykun/embedding-explore
