こんにちは~ 浮田です。
RAG を触っていると当たり前のように出てくる「1024 次元のベクトル」。
これ、何が 1024 個あるんですか?
答えは数字です。1024 次元というのは、数字が 1024 個並んだ配列のことです。
自分のブログを意味検索できるようにしたとき、Titan Text Embeddings V2 で 1024 次元にして OpenSearch に放り込んで、ちゃんと動いて満足していました。(あまり中身を理解せずに、、)
別のことを調べていて、ふと「長い文章と短い文章、どっちも 1024 個なの?」と思いました。
試したら、どっちも 1024 個でした。「こんにちは」の 5 文字(2 トークン)を入れても、800 文字の段落(約480 トークン)を入れても、返ってくる配列の長さは同じです。
自分の中でのベクトルの理解を整理して残しておきます。
前半はモデルが決める側の話、後半は自分で決める側の話です。
800 文字ぶんの情報が 1024 個の数字に圧縮されているのか、というと、そうではありません。
情報量的に無理です。
1024 次元の float32 は 約4 KB で、800 文字の日本語は 2.4 KB、数千文字なら十数 KB あります。入れた文章は、ベクトルの中には残っていません。
中で何が起きているかというと、入力の各トークンがまず 1024 個の数字の並び(内部表現)になります。480トークンなら 480 行 × 1024 列の表です。これをプーリングという処理でトークン方向に潰し、平均などで 1 行にまとめます。行が 2 行でも 480 行でも、潰した結果は 1 行 1024 個。だから長さによらず 1024 個になります。
トークンが 2 個でも 480 個でも、まとめた結果は 1 本。だから長さによらず 1024 個になります。
「東京都千代田区丸の内 1-9-1」という住所は、東京駅という建物の中身を一切含んでいません。
でも「京都駅より品川駅に近い」は、住所だけで判断できます。
埋め込みも同じです。中身は入っていないが、他と比べて近いか遠いかは分かる。
ベクトル検索に必要なのはそれだけなので、これで成立します。
個々の次元に人間が読める意味はありません。「371 番目は敬語らしさ」のような対応はしていなくて、意味を持つのは 1024 次元空間の中の位置だけです。
1024 は住所の桁数のようなもので、モデルが決める値です。自分で刻む数ではありません。
「そういうものだと思っていたこと」に、いくつか理由が付きます。
ひとつ。ベクトルから元の文章は復元できません。
だから検索結果に本文を返すには、本文を別のフィールドとして持っておく必要があります。前回のマッピングで embedding の隣に title と text を持たせていたのはこれが理由でした。
当時は「そういうものだ」と思ってました。
"mappings": {
"properties": {
"title": { "type": "keyword" },
"text": { "type": "text" },
"embedding": { "type": "knn_vector", "dimension": 1024 }
}
}
完全一致の検索が苦手な理由もこれです。
「だいたいこの辺」を探すのは得意で、「この文字列を含むもの」を探すのは苦手。
モデルの出来の問題ではなく、住所しか持っていないことの帰結です。型番やエラーコードで引きたいなら、キーワード検索を別に持つしかありません。
次元数はモデルが用意した選択肢から選ぶだけですが、ベクトルを何本作るかは自由に決められます。
ベクトルの本数はチャンクの数です。ドキュメントの数ではありません。
1 チャンクを埋め込みモデルに渡すと 1 本返ってくる。それだけです。
チャンクの切り方は大きく 3 つあります。
トークン数で機械的に区切る固定長分割、見出しや段落で切る構造単位、話題が変わったところで切る意味単位。
前回やったのは見出し単位です。Bedrock のナレッジベースに任せると、既定では 300 トークン前後で、文の途中では切らないように区切ってくれます。
縦に並ぶ本数がベクトル数で、これは自分で決める。横の長さが次元数で、これはモデルが決める
本数を減らしたければ、1 チャンクを長くすればいい。そう思うかもしれませんが、デメリットもあります。
住所のたとえで言うと、短いチャンクは東京駅という 1 つの建物の住所です。長いチャンクは、渋谷・新宿・池袋を全部含んだエリアの真ん中の住所になります。
座標としては正しいけれど、そこに何があるかというと、誰の家でもない中間地点です。
「渋谷の店を探している」と聞かれても、その真ん中の住所はどこからも中途半端に遠い。
「長いチャンクは何を聞いても中くらいに似ている」ということです。
Titan Text Embeddings V2 は 8192 トークンまで入ります。ただ公式ドキュメントには、検索用途では段落やセクションのような論理的な単位に分割することを推奨する、と書いてあります。上限は入力の限界であって、推奨値ではありません。
見出しではなく固定長で切る場合、境界で文脈が切れます。
「S3 Vectors はインデックス作成後に次元数を変更できません。これを知らずに進めると作り直しになります。」が真ん中で切れると、後半のチャンクは「これ」が何なのか分からないベクトルになります。
前のチャンクの末尾を次のチャンクの頭に重ねておけば、境界をまたいだ文脈が残ります。重複率は 10〜20% あたりが出発点で、Bedrock のナレッジベースの固定サイズチャンキングでは、チャンクあたりのトークン数と重複率を指定できます。
副作用は 2 つあります。ベクトルの本数が増えること(20% 重ねれば約 2 割増で、ストレージもクエリの費用も乗ります)。それから、検索結果に重複が出ること。重複区間に刺さるクエリでは隣り合うチャンクが両方ヒットするので、RAG なら LLM に渡す前に重複を落とす処理が要ります。
見出し単位で切るなら、見出しがそもそも意味の境界なので、オーバーラップの必要性は下がります。代わりに効いたのは、チャンク本文の頭に「記事タイトル > 大見出し > 中見出し」を差し込んでからベクトル化することでした。これは前回そうしていて、今回もそのままです。
ちなみに、上限そのものも日本語では思ったより手前にあります。仕様上は 50,000 文字ですが、これは英語の数字で、日本語では 9,000 文字あたりでトークンの上限に当たり、400 エラーで弾かれました(詳しくは別の記事に書きました)。
ベクトルは文章ではなく住所です。中身は入っていないが、近い遠いは分かる。
次元数は住所の桁数で、モデルが決める値です。
これが腑に落ちてから、「なぜ本文を別に持つのか」「なぜ完全一致が苦手なのか」「なぜ長いチャンクが効かないのか」が理解できました。
自分で決めるのは、どこで切るかと、切れ目をどう扱うかだけ。長いチャンクはエリアの真ん中の住所になるので、論理的な単位で切る。固定長で切るならオーバーラップで境界の文脈を残し、見出しで切るなら見出しパスを本文の頭に付ける。
※この記事は 2026 年 9 月時点の仕様に基づきます。