エンベディング(埋め込み)とは、文章を「意味の近さ」で比べられる数値の並び(ベクトル)に変えたものです。 ベクトル検索は、質問文のベクトルと文書のベクトルの近さを計算し、近い順に並べるだけの仕組みです。

この記事では、3次元の小さなベクトルを使ってコサイン類似度・内積・ユークリッド距離を手で計算し、NumPyで検算します。あわせて、検索の精度を左右するチャンク分割の4方式を、そのまま使えるPython関数と分割結果で比べます。

確認環境:Ubuntu 24.04、Python 3.11.15、NumPy 2.4.4(確認日 2026-10-06)。図はMermaid(@mermaid-js/mermaid-cli 12.0.0)で描画できることを確かめています。記事中のベクトルは仕組みを示すために手で置いた説明用の小さな例で、埋め込みモデルが出力した値ではありません。

この記事の結論

  • エンベディングは文章を数値の並びに変えたもの。意味が近い文章ほど、ベクトルの向きが近くなるように作られます。
  • ベクトル検索は「全件との類似度を計算して、上位k件を取る」だけ。NumPyなら数行で書けます。
  • 指標は3つ(コサイン類似度・内積・ユークリッド距離)。ベクトルの長さを1にそろえる(正規化する)と、3つとも同じ順位になります。
  • 正規化していないベクトルでは、3つの指標で順位がすべて変わることがあります(この記事の例で確認)。
  • チャンク分割は、見出しのある文書なら「見出し単位で分けてから、長い節を文単位で詰め直す」組み合わせが、話題をまたがない分け方になります。

エンベディングとベクトル検索の早見表

用語 ひとことで 補足
エンベディング(埋め込み) 文章を数値の並びに変えたもの 例:1,536個や3,072個の小数の並び(次元数はモデルごとに決まる)
ベクトル化 文章をエンベディングに変える操作 埋め込みモデルに文章を渡して行う
次元 ベクトルに含まれる数値の個数 この記事では計算しやすいよう3次元で説明
ベクトル検索 ベクトルの近さの順に文書を並べる検索 言い換えや表記ゆれに強い
コサイン類似度 2つのベクトルの向きの近さ(-1〜1) 1に近いほど似ている。長さの影響を受けない
内積 向きの近さ × 長さ 正規化済みならコサイン類似度と同じ値
ユークリッド距離 2点の直線距離 小さいほど似ている
正規化 ベクトルの長さを1にそろえること 各要素を長さで割る
チャンク 文書を分割した1つ分のかたまり チャンクごとにベクトルを作って保存する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「エンベディングとは?ベクトル検索とコサイン類似度を計算例で解説」

エンベディングとは:文章を数値の並びに変える

OpenAI の公式ガイドは、エンベディングを「浮動小数点数のベクトル(リスト)」であり、「2つのベクトルの距離が関連の度合いを表す」と説明しています(出典は記事末尾)。つまり、文章そのものではなく数値どうしを比べることで、「似ているかどうか」を計算できるようにしたものです。

文字が一致しているかではなく意味で比べるので、次のような組み合わせも近いと判定できます。

質問文 文書の文 文字の一致 意味
備品を借りたい プロジェクターの予約方法 ほぼ無し 近い
休みを申請したい 休暇の届け出の手順 ほぼ無し 近い
備品を借りたい 食堂の営業時間 無し 遠い

登録と検索の流れ

flowchart LR
    subgraph 登録するとき
        D[文書] --> C[チャンクに分割]
        C --> E1[埋め込みモデル]
        E1 --> V[(ベクトルを保存)]
    end
    subgraph 検索するとき
        Q[質問文] --> E2[同じ埋め込みモデル]
        E2 --> S[類似度を計算]
        V --> S
        S --> R[近い順に上位k件]
    end

大事な点は2つです。登録と検索で同じ埋め込みモデルを使うこと(モデルが違うと数値の意味が合わなくなります)、そして文書は丸ごとではなくチャンクに分けて登録することです。チャンク分割は記事の後半で扱います。

コサイン類似度・内積・ユークリッド距離を手計算する

ここからは、3次元のベクトルで実際に計算します。質問文のベクトルを q、文書のベクトルを A〜E とします。

名前 ベクトル 位置づけ(説明用)
q (2, 1, 0) 質問文
A (4, 2, 0) q と同じ向きで、長さが2倍
B (2, 1, 1) q のすぐ近くにある点
C (1, 2, 0) 向きが少し違う
D (1, 6, 0) 向きはかなり違うが、長い
E (0, 0, 3) q と直角(共通する成分が無い)

3つの式

内積            q・d = q1×d1 + q2×d2 + q3×d3
長さ            |v|  = √(v1² + v2² + v3²)
コサイン類似度  cos  = (q・d) ÷ (|q| × |d|)
ユークリッド距離 dist = √((q1-d1)² + (q2-d2)² + (q3-d3)²)

q と B の計算(途中式つき)

内積    2×2 + 1×1 + 0×1 = 5
|q|     √(2² + 1² + 0²) = √5 = 2.2361
|B|     √(2² + 1² + 1²) = √6 = 2.4495
cos     5 ÷ (2.2361 × 2.4495) = 5 ÷ 5.4772 = 0.9129
dist    √((2-2)² + (1-1)² + (0-1)²) = √1 = 1.0000

全文書の手計算の結果

文書 内積 長さ コサイン類似度 ユークリッド距離
A (4,2,0) 8+2+0 = 10 √20 = 4.4721 10 ÷ (2.2361×4.4721) = 1.0000 √(4+1+0) = 2.2361
B (2,1,1) 4+1+0 = 5 √6 = 2.4495 5 ÷ 5.4772 = 0.9129 √(0+0+1) = 1.0000
C (1,2,0) 2+2+0 = 4 √5 = 2.2361 4 ÷ 5 = 0.8000 √(1+1+0) = 1.4142
D (1,6,0) 2+6+0 = 8 √37 = 6.0828 8 ÷ 13.6015 = 0.5882 √(1+25+0) = 5.0990
E (0,0,3) 0+0+0 = 0 √9 = 3.0000 0 ÷ 6.7082 = 0.0000 √(4+1+9) = 3.7417

この表から、指標ごとの1位が変わることが読み取れます。

  • コサイン類似度の1位は A:向きが完全に同じなので 1.0。長さが2倍でも値は変わりません。
  • ユークリッド距離の1位は B:すぐ隣の点なので距離 1。A は向きが同じでも、長い分だけ遠くなります。
  • 内積の2位は D:向きはあまり合っていませんが、長いので値が大きくなります。

NumPyで検算する(コピペで動くコード)

手計算が合っているかを NumPy で確かめます。pip install numpy のあと、次のファイルを python similarity.py で実行します。

# similarity.py  3次元の小さなベクトルで、3つの指標を計算する(説明用の小さな例)
import numpy as np

query = np.array([2.0, 1.0, 0.0])
docs = {
    "A": np.array([4.0, 2.0, 0.0]),
    "B": np.array([2.0, 1.0, 1.0]),
    "C": np.array([1.0, 2.0, 0.0]),
    "D": np.array([1.0, 6.0, 0.0]),
    "E": np.array([0.0, 0.0, 3.0]),
}


def cosine(a, b):
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))


def report(q, items, title):
    print(title)
    print("文書  内積     コサイン類似度  ユークリッド距離")
    rows = {}
    for name, d in items.items():
        rows[name] = (float(q @ d), cosine(q, d), float(np.linalg.norm(q - d)))
        print(f"{name}    {rows[name][0]:7.4f}  {rows[name][1]:.4f}          {rows[name][2]:.4f}")
    print("内積の大きい順        :", sorted(rows, key=lambda n: -rows[n][0]))
    print("コサインの大きい順    :", sorted(rows, key=lambda n: -rows[n][1]))
    print("距離の小さい順        :", sorted(rows, key=lambda n: rows[n][2]))
    print()


report(query, docs, "== 正規化する前 ==")

unit = lambda v: v / np.linalg.norm(v)  # 長さを1にそろえる
report(unit(query), {n: unit(d) for n, d in docs.items()}, "== 正規化した後(長さ1) ==")

# 正規化済みなら 距離の2乗 = 2 - 2×コサイン類似度
for name, d in docs.items():
    left = float(np.linalg.norm(unit(query) - unit(d)) ** 2)
    right = 2 - 2 * cosine(query, d)
    print(f"{name}: 距離の2乗={left:.4f}  2-2cos={right:.4f}")

実行結果:

== 正規化する前 ==
文書  内積     コサイン類似度  ユークリッド距離
A    10.0000  1.0000          2.2361
B     5.0000  0.9129          1.0000
C     4.0000  0.8000          1.4142
D     8.0000  0.5882          5.0990
E     0.0000  0.0000          3.7417
内積の大きい順        : ['A', 'D', 'B', 'C', 'E']
コサインの大きい順    : ['A', 'B', 'C', 'D', 'E']
距離の小さい順        : ['B', 'C', 'A', 'E', 'D']

== 正規化した後(長さ1) ==
文書  内積     コサイン類似度  ユークリッド距離
A     1.0000  1.0000          0.0000
B     0.9129  0.9129          0.4174
C     0.8000  0.8000          0.6325
D     0.5882  0.5882          0.9076
E     0.0000  0.0000          1.4142
内積の大きい順        : ['A', 'B', 'C', 'D', 'E']
コサインの大きい順    : ['A', 'B', 'C', 'D', 'E']
距離の小さい順        : ['A', 'B', 'C', 'D', 'E']

A: 距離の2乗=0.0000  2-2cos=0.0000
B: 距離の2乗=0.1743  2-2cos=0.1743
C: 距離の2乗=0.4000  2-2cos=0.4000
D: 距離の2乗=0.8237  2-2cos=0.8237
E: 距離の2乗=2.0000  2-2cos=2.0000

手計算の表と一致しました。そして、正規化する前は3つの順位がすべて違い、正規化した後は3つとも A, B, C, D, E で一致しています。

なぜ正規化すると順位がそろうのか

長さが1のベクトルどうしでは、次の関係が成り立ちます。

内積 = コサイン類似度                (分母の |q|×|d| が 1×1 になるため)
距離の2乗 = 2 - 2 × コサイン類似度   (コサインが大きいほど距離は小さい)

実行結果の最後の5行が、この2つ目の式の確認です(B なら 0.1743 と 0.1743)。コサイン類似度が大きい順と、距離が小さい順は、必ず同じ並びになります。

3つの指標の違いと選び方

指標 見ているもの 値の向き 長さの影響 向いている場面
コサイン類似度 向きの近さ 大きいほど近い(-1〜1) 受けない 正規化されているか分からないとき。まず選ぶ指標
内積 向きの近さ × 長さ 大きいほど近い 受ける 正規化済みのベクトルを速く比べたいとき
ユークリッド距離 2点の直線距離 小さいほど近い 受ける 長さにも意味があるデータ、クラスタリング

条件から選ぶ判断表

条件 選ぶ指標 理由
埋め込みモデルの説明に「長さ1に正規化済み」と書いてある 内積(またはコサイン類似度) 順位は同じ。内積は割り算が無い分だけ計算が軽い
正規化されているか分からない コサイン類似度 長さの違いに左右されない
自分で作った特徴量など、長さにも意味がある ユークリッド距離か内積 長さの情報を捨てずに比べられる
複数のモデルやデータベースで値を見比べたい コサイン類似度 -1〜1 に収まるので、しきい値を決めやすい
迷っている 保存する前に正規化して、内積を使う 3つのどれでも同じ順位になるので、指標の選び方で結果が変わらなくなる

OpenAI の公式ガイドには、同社の埋め込みは長さ1に正規化されていると書かれています。このようなモデルでは、内積とコサイン類似度のどちらを使っても順位は変わりません。使うモデルが正規化済みかどうかは、そのモデルの公式ドキュメントで確かめるか、np.linalg.norm(vector) が 1.0 になるかを見れば分かります。

データベースで使うときの演算子(pgvector の例)

PostgreSQL の拡張である pgvector の README(最新のインストール手順は v0.8.7)には、次の演算子が載っています。

演算子 意味 並べ方
<-> ユークリッド距離(L2距離) 小さい順
<#> 内積に -1 を掛けた値 小さい順
<=> コサイン距離 小さい順
<+> L1距離(各要素の差の絶対値の合計) 小さい順

README には、<#> は内積に -1 を掛けた値を返す(PostgreSQL のインデックスが昇順の走査だけに対応しているため)と書かれています。内積そのものが欲しいときは、結果に -1 を掛けます。コサイン「距離」も同じ考え方で、1 からコサイン類似度を引いた値です。「類似度は大きいほど近い、距離は小さいほど近い」と覚えておくと、並べ替えの向きを取り違えません。

ベクトル検索の本体は数行で書ける

ベクトル検索の中身は、全件との類似度を計算して上位k件を取ることです。

# search.py  ベクトル検索の本体は「全件の類似度を計算して上位k件を取る」だけ(説明用の小さな例)
import numpy as np

texts = ["プロジェクターの予約方法", "会議室の予約ルール", "勤怠の締め日", "食堂の営業時間"]
vectors = np.array([      # 本来は埋め込みモデルが作る。ここでは手で置いた3次元の値
    [0.9, 0.1, 0.0],
    [0.8, 0.2, 0.1],
    [0.1, 0.9, 0.1],
    [0.0, 0.2, 0.9],
])
query_vec = np.array([0.85, 0.15, 0.05])   # 「備品を借りたい」に当たる値(手で置いたもの)


def top_k(query, matrix, k=2):
    m = matrix / np.linalg.norm(matrix, axis=1, keepdims=True)   # 行ごとに長さ1へ
    q = query / np.linalg.norm(query)
    scores = m @ q                      # 正規化済みなので内積=コサイン類似度
    order = np.argsort(-scores)[:k]     # 大きい順にk件
    return [(int(i), float(scores[i])) for i in order]


for i, score in top_k(query_vec, vectors, k=3):
    print(f"{score:.4f}  {texts[i]}")

実行結果:

0.9963  プロジェクターの予約方法
0.9956  会議室の予約ルール
0.2857  勤怠の締め日

行列とベクトルの掛け算(m @ q)1回で、全文書との類似度がまとめて計算できます。件数が増えて全件計算が重くなったときに、近いものだけを素早く探すための索引(近似最近傍探索)を使いますが、求めているものは同じ「近い順の上位k件」です。

キーワード検索との違いと使い分け

観点 キーワード検索 ベクトル検索
比べるもの 文字や単語の一致 意味の近さ(ベクトルの近さ)
言い換え(借りる/予約する) 一致しないと見つからない 見つけやすい
型番・エラーコード・人名など固有の文字列 得意(一致すれば確実) 似た別の文字列が上位に来ることがある
結果の説明のしやすさ 「この語を含むから」と言える 「数値が近いから」としか言えない
準備 索引を作る 埋め込みモデルでベクトル化して保存する
文書を更新したとき 索引を更新 そのチャンクのベクトルを作り直す

条件から選ぶ判断表

条件 選ぶ検索
型番・コード・ID など、決まった文字列で探す キーワード検索
質問文が話し言葉で、文書と言い回しが違う ベクトル検索
「なぜこの結果が出たか」を利用者に説明する必要がある キーワード検索を主にする
両方の探し方が混ざる(社内の手引きや問い合わせ対応など) 両方の結果を合わせる(ハイブリッド検索)
文書が数十件で、利用者が語を知っている キーワード検索で足りる

チャンク分割の4方式をコードで比べる

文書を丸ごと1つのベクトルにすると、複数の話題が混ざって、どの質問にもほどほどにしか近くならないベクトルになります。そこで文書をチャンクに分け、チャンクごとにベクトルを作ります。分け方によって「検索で当たるかたまり」が変わるので、ここが精度に直結します。

次の1ファイルに4方式の関数を入れました。標準ライブラリだけで動きます(python chunking.py)。

# chunking.py  チャンク分割の4方式(標準ライブラリのみ)
import re

TEXT = """# 備品予約マニュアル

## 予約の方法
予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。申請後、担当者の承認で確定します。

## 返却の方法
利用が終わったら当日中に総務の棚へ戻します。付属のケーブルもそろえて返してください。

## よくある問い合わせ
延長したいときは、次の予約が入っていなければポータルから延長できます。"""


def fixed_chunks(text: str, size: int) -> list[str]:
    """固定長:size文字ごとに切る"""
    return [text[i:i + size] for i in range(0, len(text), size)]


def overlap_chunks(text: str, size: int, overlap: int) -> list[str]:
    """重なりあり:size文字ごとに切り、前のチャンクとoverlap文字だけ重ねる"""
    if not 0 <= overlap < size:
        raise ValueError("overlap は 0 以上 size 未満にします")
    step = size - overlap
    return [text[i:i + size] for i in range(0, len(text), step) if i == 0 or i + overlap < len(text)]


def heading_chunks(markdown: str) -> list[str]:
    """見出し単位:Markdownの見出し行(#〜######)の直前で切る"""
    parts = re.split(r"(?m)^(?=#{1,6} )", markdown)
    return [p.strip() for p in parts if p.strip()]


def sentence_chunks(text: str, max_chars: int) -> list[str]:
    """文単位:句点(。!?)の直後で文に分け、max_charsを超えない範囲で詰める"""
    sentences = [s.strip() for s in re.split(r"(?<=[。!?])", text) if s.strip()]
    chunks, current = [], ""
    for s in sentences:
        if current and len(current) + len(s) > max_chars:
            chunks.append(current)
            current = ""
        current += s
    if current:
        chunks.append(current)
    return chunks


def show(title: str, chunks: list[str]) -> None:
    print(f"== {title}:{len(chunks)}個 ==")
    for i, c in enumerate(chunks):
        print(f"[{i}] {len(c):>3}字 | {c.replace(chr(10), '⏎')}")
    print()


body = TEXT.split("\n", 2)[2]          # 固定長・重なり・文単位は、題名を除いた本文で比べる
show("固定長 60字", fixed_chunks(body, 60))
show("重なりあり 60字・重なり15字", overlap_chunks(body, 60, 15))
show("見出し単位", heading_chunks(TEXT))
show("文単位 上限60字", sentence_chunks(body, 60))

try:
    overlap_chunks(body, 60, 60)
except ValueError as e:
    print("ValueError:", e)

# 組み合わせ:見出しで分けてから、長い節だけ文単位で詰め直す
combined = [c for section in heading_chunks(TEXT) for c in sentence_chunks(section, 60)]
show("見出し単位 → 文単位 上限60字", combined)

実行結果(⏎ は改行の位置):

== 固定長 60字:3個 ==
[0]  60字 | ## 予約の方法⏎予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。申請後、担当者の
[1]  60字 | 承認で確定します。⏎⏎## 返却の方法⏎利用が終わったら当日中に総務の棚へ戻します。付属のケーブルもそろえて返してくださ
[2]  52字 | い。⏎⏎## よくある問い合わせ⏎延長したいときは、次の予約が入っていなければポータルから延長できます。

== 重なりあり 60字・重なり15字:4個 ==
[0]  60字 | ## 予約の方法⏎予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。申請後、担当者の
[1]  60字 | してください。申請後、担当者の承認で確定します。⏎⏎## 返却の方法⏎利用が終わったら当日中に総務の棚へ戻します。付属の
[2]  60字 | 中に総務の棚へ戻します。付属のケーブルもそろえて返してください。⏎⏎## よくある問い合わせ⏎延長したいときは、次の予約
[3]  37字 | せ⏎延長したいときは、次の予約が入っていなければポータルから延長できます。

== 見出し単位:4個 ==
[0]  11字 | # 備品予約マニュアル
[1]  69字 | ## 予約の方法⏎予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。申請後、担当者の承認で確定します。
[2]  51字 | ## 返却の方法⏎利用が終わったら当日中に総務の棚へ戻します。付属のケーブルもそろえて返してください。
[3]  48字 | ## よくある問い合わせ⏎延長したいときは、次の予約が入っていなければポータルから延長できます。

== 文単位 上限60字:4個 ==
[0]  52字 | ## 予約の方法⏎予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。
[1]  48字 | 申請後、担当者の承認で確定します。## 返却の方法⏎利用が終わったら当日中に総務の棚へ戻します。
[2]  20字 | 付属のケーブルもそろえて返してください。
[3]  48字 | ## よくある問い合わせ⏎延長したいときは、次の予約が入っていなければポータルから延長できます。

ValueError: overlap は 0 以上 size 未満にします
== 見出し単位 → 文単位 上限60字:5個 ==
[0]  11字 | # 備品予約マニュアル
[1]  52字 | ## 予約の方法⏎予約は社内ポータルの備品ページから行います。利用日の前日17時までに申請してください。
[2]  17字 | 申請後、担当者の承認で確定します。
[3]  51字 | ## 返却の方法⏎利用が終わったら当日中に総務の棚へ戻します。付属のケーブルもそろえて返してください。
[4]  48字 | ## よくある問い合わせ⏎延長したいときは、次の予約が入っていなければポータルから延長できます。

4方式の比較表

方式 切る位置 今回の結果 向いている文書
固定長 文字数だけで決める 「返してくださ/い。」のように文の途中で切れた 見出しも句点も無いログやデータ
重なりあり 固定長+前後を重ねる 切れ目の文が前後どちらかに丸ごと入る。チャンク数は3→4に増えた 切れ目の情報を落としたくない長文
見出し単位 Markdown の見出し行 1節=1チャンク。話題が混ざらない 手引き・仕様書・FAQ など見出しのある文書
文単位 句点の直後 文の途中では切れない。ただし[1]で前の節の文と次の節が同じチャンクに入った 見出しの無い文章、会話の記録
見出し単位 → 文単位 見出しで分け、長い節だけ文で詰め直す 話題をまたがず、上限60字にも収まった 見出しがあり、節の長さにばらつきがある文書

条件から選ぶ判断表

文書の特徴 選ぶ方式
Markdown や HTML の見出しがある 見出し単位(節が長ければ文単位と組み合わせる)
見出しは無いが、句点で終わる文章 文単位
構造が無い、または構造を信用できない 重なりありの固定長
1件が短い(FAQ の1問1答、1行1件のデータ) 分割しない(1件=1チャンク)

チャンクの大きさと重なりの幅に、どの文書にも合う決まった値はありません。よく聞かれる質問を10〜20件用意し、期待するチャンクが上位に入るかを数えて決めるのが確実です。大きさを変えたら、その都度同じ質問で数え直します。

なお、埋め込みモデルには1回に渡せる長さの上限があります(OpenAI の公式ガイドでは、掲載されている2つのモデルとも最大 8,192 トークン)。チャンクはこの上限より十分小さくします。

確かめて分かったこと

  • 正規化していないベクトルでは、3つの指標の順位がすべて変わりました。 内積は A, D, B, C, E、コサイン類似度は A, B, C, D, E、ユークリッド距離は B, C, A, E, D。同じデータでも、指標を変えると検索結果が変わります。
  • 向きが同じ A は、コサイン類似度では1位、ユークリッド距離では3位でした。 長さが2倍あるため、距離で測ると遠くなります。
  • 長いベクトル D は、内積だと2位に上がりました。 内積は長さの影響を受けるので、正規化していないデータでは長いベクトルが有利になります。
  • 正規化すると3つの順位が一致し、「距離の2乗 = 2 - 2×コサイン類似度」も数値で一致しました。
  • 文単位の分割だけでは、節をまたぐチャンクができました(「承認で確定します。」と「返却の方法」が同じチャンクに入った)。先に見出しで分けると解消しました。
  • 重なり幅をチャンクの大きさと同じにすると、切り出し位置が進まなくなります。 関数の先頭で ValueError にして気づけるようにしました。

よくある質問

Q. エンベディングとベクトル化の違いは? A. 指しているものが違うだけで、同じ話題の言葉です。文章を数値の並びに変える操作が「ベクトル化」、できあがった数値の並びが「エンベディング(埋め込み)」です。

Q. コサイン類似度と内積の違いは? A. コサイン類似度は向きだけを見て、内積は向きと長さの両方を見ます。長さを1にそろえたベクトルでは2つは同じ値になり、順位も同じです。

Q. コサイン類似度はいくつ以上なら「似ている」と言えますか? A. どのモデルにも共通する基準の値はありません。モデルや文書によって値の出方が違うため、手元の質問と正解の組で値の分布を見て、しきい値を決めます。まずは「上位k件を取る」形にして、しきい値は後から足すと進めやすくなります。

Q. ベクトル検索で期待した文書が出てこないときは? A. まずチャンクの分け方を確かめます。答えになる文が2つのチャンクに分かれていないか、1つのチャンクに複数の話題が混ざっていないかを、分割結果を出力して目で見るのが近道です。あわせて、登録と検索で同じ埋め込みモデルを使っているか、並べ替えの向き(類似度は大きい順、距離は小さい順)が合っているかを確認します。

まとめ

  • エンベディングは、文章を意味の近さで比べられる数値の並びに変えたものです。
  • ベクトル検索は、類似度を計算して近い順に並べるだけ。NumPy なら行列の掛け算1回で全件を計算できます。
  • コサイン類似度は向き、内積は向き×長さ、ユークリッド距離は直線距離を見ています。
  • 正規化(長さ1)すれば3つの指標は同じ順位になります。迷ったら正規化して内積を使います。
  • 類似度は大きい順、距離は小さい順。データベースの演算子は距離を返すものが多いので、並べ替えの向きを確かめます。
  • チャンク分割は、見出しがあれば見出し単位、長い節は文単位で詰め直します。構造が無ければ重なりありの固定長を使います。
  • チャンクの大きさは、質問と正解の組で数えて決めます。

参考・出典

  • OpenAI「Vector embeddings」 https://developers.openai.com/api/docs/guides/embeddings (2026-10-06 閲覧。エンベディングの説明、長さ1への正規化、次元数、入力の上限)
  • pgvector README https://github.com/pgvector/pgvector (2026-10-06 閲覧。距離の演算子、<#> の戻り値)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「エンベディングとは?ベクトル検索とコサイン類似度を計算例で解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?