在宅勤務とリモートワークはどちらも同じような意味ですが、文字列の一致度で探すキーワード検索では、片方のワードで検索したときにもう一方のワードは探すことができません。
embeddingは、文章を意味を表す数値の並び(ベクトル)に変換したもので、意味が近い文章どうしはベクトルも近くなります。この特性を用いて意味の近いワードを検索することで、文字列は異なるけれども意味が近い言葉を検索することができます。
この記事では、embeddingがどういうもので、意味の近さをどう数値で測るのかを、実際に文章をベクトル化して近い文を探しながら確認します。なお、アルゴリズムの中身までは踏み込まず、既存のライブラリを使用します。
本文の記述は2026年7月時点のものです。
目次
はじめに
この記事の目的は、文章をベクトルに変換し、文章どうしの意味の近さを数値で出せるようになることです。この技術は、意味で文章を探す意味検索や、その検索を土台にするRAGの中核になります。
前準備として、次のライブラリを使用します。
pip install sentence-transformers
# uvの場合
uv add sentence-transformersEmbeddingとは
embedding(埋め込み)は、テキストを、意味を表す数値ベクトルに変換したものです。ベクトルとは数値の組です。
ベクトルを構成する数値の個数は次元といい、上式の場合はn次元と呼びます。
各次元に対応する軸の数値は、「丁寧さ」「話題」のように人間が意味を与えられるものではなく、モデルが学習によって決めた抽象的な値です。したがって、意味は軸を一本ずつ読み取ることではなく、ベクトル全体が空間のどこに位置するか(他のベクトルとの距離や方向)に現れます。
embeddingの一番大事な性質は「意味が近い文章どうしはベクトルも近くなる」ということです。
例えば、「在宅勤務」と「リモートワーク」は、この空間では近くに置かれます。あとは、2つのベクトルの距離を数値で測れれば、意味の近さを評価できます。
文章を埋め込んで意味の近さを測る
モデルを読み込んで1文を埋め込む
embeddingを作るには、テキストをベクトルに変換する埋め込みモデルが必要です。埋め込みモデル自体も大量のテキストを処理することで作成します。古典的な方法は統計処理を用いますが、現在はニューラルネットワークによる学習がよく使われます。
モデルの概念を確かめるために手軽に使えるライブラリとして、sentence-transformersparaphrase-multilingual-MiniLM-L12-v2を使います。
モデルを読み込んで、1つの文章をベクトルに変換するには、次のようにします。SentenceTransformer
from sentence_transformers import SentenceTransformer
import numpy as np
# 埋め込みモデルを読み込む
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
# 1文をベクトルにする
vec = model.encode("在宅勤務は週3日まで認められています。")
print("次元数:", vec.shape[0])
print("先頭5要素:", np.round(vec[:5], 4))実行すると、次の結果が得られました。
次元数: 384
先頭5要素: [ 0.0739 -0.3273 0.0347 -0.1958 0.1846]
上記のように、model.encode(文章)numpy.ndarrayで返されます。
ベクトルの近さを測る(コサイン類似度)
ベクトル同士の近さの測り方にはいくつかありますが、RAGで最もよく使われるのはコサイン類似度です。
コサイン類似度は2つのベクトルの成す角度の余弦を計算したものです。
2つのベクトルの向きがどのくらいそろっているかを表す数値で-1から1の値をとります。向きが同じ(意味が近い)ほど1に近く、無関係だと0に近く、反対方向(意味が逆)だと-1に近くなります。
コサイン類似度で文章の意味の近さを評価するコードの例を示します。この例では4つの文章をそれぞれのベクトルに変換して、コサイン類似度を計算してみます。
from sentence_transformers import SentenceTransformer
import numpy as np
def cosine(a, b):
# コサイン類似度
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
docs = [
"在宅勤務は週3日まで認められています。", # 文章①
"リモートワークは週に3回まで可能です。", # 文章②
"経費精算は毎月20日が締め切りです。", # 文章③
"会社の休日はカレンダー通りです。", # 文章④
]
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
emb = model.encode(docs)
print("在宅勤務① vs リモートワーク②:", round(cosine(emb[0], emb[1]), 3))
print("在宅勤務① vs 経費精算③ :", round(cosine(emb[0], emb[2]), 3))
print("在宅勤務① vs 会社の休日④ :", round(cosine(emb[0], emb[3]), 3))結果は次のようになりました。
在宅勤務① vs リモートワーク②: 0.771
在宅勤務① vs 経費精算③ : 0.165
在宅勤務① vs 会社の休日④ : 0.198
在宅勤務とリモートワークの文章は文字が重なりませんが、コサイン類似度は高い値を示しています。これはキーワード検索ではできなかったことで、「意味が近い=ベクトルが近い」を確認できました。
ベクトルの正規化
コサイン類似度は、ベクトルの内積をそれぞれのベクトルの長さの積で割りますが、あらかじめベクトルの長さを1にしておけば、内積はそのままコサイン類似度と一致します。このように、ベクトルの長さを1にそろえる処理を正規化とよびます。
sentence-transformersでは、encodeにnormalize_embeddings=Trueを渡すと、長さ1にそろえたベクトルが得られます。
from sentence_transformers import SentenceTransformer
import numpy as np
docs = [
"在宅勤務は週3日まで認められています。",
"リモートワークは週に3回まで可能です。",
]
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
emb = model.encode(docs)
emb_n = model.encode(docs, normalize_embeddings=True) # ベクトルの正規化を実施
print("正規化前の長さ:", round(float(np.linalg.norm(emb[0])), 3))
print("正規化後の長さ:", round(float(np.linalg.norm(emb_n[0])), 3))
print("正規化後の内積:", round(float(np.dot(emb_n[0], emb_n[1])), 3))在宅勤務とリモートワークの2つの文に対して、結果は次のようになりました。正規化後の内積は前回計算したコサイン類似度と一致しています。
正規化前の長さ: 4.153
正規化後の長さ: 1.0
正規化後の内積: 0.771
正規化によってベクトルの長さをそろえておけば、内積という軽い計算で意味の近さを測れるため、ベクトルを大量に保存して高速に検索する場面では、この違いが効いてきます。
モデルによっては最初から正規化された状態でベクトルを返すものもあります。
クエリに近い文を探す
意味の近さが測れれば、「質問(クエリ)に一番近い文章を選ぶ」ということができるようになります。クエリを埋め込みモデルでベクトル化して、各文章との類似度を測り、高い順に並べます。
from sentence_transformers import SentenceTransformer
import numpy as np
# モデルの読み込み
model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
# コサイン類似度
def cosine(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
# クエリとの間の類似度の高い順に文章を並べて返す
def search(docs, q_emb, doc_emb, top_k=2):
scored = [(cosine(q_emb, doc_emb[i]), docs[i]) for i in range(len(docs))]
scored.sort(reverse=True) # 類似度が高い順に並べる
return scored[:top_k]
# クエリの定義
query = "テレワークは何日できますか?"
# 文章の定義
docs = [
"在宅勤務は週3日まで認められています。",
"リモートワークは週に3回まで可能です。",
"経費精算は毎月20日が締め切りです。",
"会社の休日はカレンダー通りです。",
]
# ベクトル化
q_emb = model.encode(query)
doc_emb = model.encode(docs)
for score, d in search(docs, q_emb, doc_emb):
print(f"{score:.3f} {d}")結果は次のようになりました。
0.606 リモートワークは週に3回まで可能です。
0.473 在宅勤務は週3日まで認められています。
質問(クエリ)はテレワークという文章に含まれていない文字を使用していますが、リモートワークと在宅勤務という類似した意味の文章にヒットしていることを確認できます。
なお、embeddingモデルはLLMと異なり、同じモデルと同じ入力を使用した場合には同じ出力が得られます。
クエリと文章は同じモデルで埋め込む
embeddingのベクトル空間「意味の空間」は、モデルごとに作られ方が異なります。あるモデルが作った空間と別のモデルが作った空間は、座標軸の意味が異なります。
このため、文章をモデルAで埋め込み、クエリをモデルBで埋め込んで類似度を測っても、意味の近さを正しく評価できません。このため、「クエリと文章は同じモデルで埋め込む」ことが必須です。
同じ理由で、後から埋め込みモデルを別のものに変える場合には、保存済みの文章もすべて新しいモデルで埋め込み直す必要があります。古いモデルと新しいモデルのベクトルは、混ぜて比べられないためです。
Embeddingモデルの選択
Embeddingモデルは無料のものから有料のものまで多数公開されています。このため、いくつかの軸で用途に応じてモデルを選択します。
| 項目 | 内容 |
|---|---|
| 精度 | 意図した文章をどれだけ正しく引けるかで最も重要です。最初は公開ベンチマークなどを参考にして、最終的には自分のデータで試して決定します。 |
| 対応言語 | 英語中心のモデルにするか、多言語・日本語に対応したモデルにするかなどです。 |
| 最大入力長 | 一度に埋め込めるトークン数の上限です。長い文章を1つのベクトルにしたい場合に効きます。これを超える文章は、分割して埋め込むことになります。 |
| 次元数 | ベクトルの要素数です。大きいほど表現力が上がりやすい一方で、保存に必要な容量が増え、検索も重くなります。 |
| コスト | API型は入力トークン量に応じて課金され、セルフホスト型はGPUなどの自前のインフラ費用がかかります。 |
| ライセンス | オープンなモデルは自分の環境で動かせますが、商用利用の可否はライセンスによります。なお、今回使ったparaphrase-multilingual-MiniLM-L12-v2はApache-2.0で、商用利用できます。 |
| API型/セルフホスト型 | APIを呼ぶだけの手軽さを採るか、自前で動かしてデータを外に出さないことや大量処理時のコストを抑えるかのトレードオフです。 |
その他、次のような機能をもっているかもモデル選択時の参考になります。
| 項目 | 内容 |
|---|---|
| MRL (Matryoshka Representation Learning) | ベクトルの次元の切り詰めを行う仕組みです。必要に応じてベクトルの一部を使うなどです。保存コストと検索速度を調整するために使います。 |
| マルチモーダルembedding | テキストだけではなく、画像なども同じベクトル空間に埋め込む技術です。画像も意味検索の対象にしたいときに用います。 |
モデルは次節の公開ベンチマークを参考にして選ぶのがよいと思います。
モデルの公開ベンチマーク
モデルが多くて選べない場合に、比較の出発点になるのが公開ベンチマークです。
代表的なものにMTEB(Massive Text Embedding Benchmark)と、その多言語版のMMTEB(Massive Multilingual Text Embedding Benchmark)があります。
これらは、多数のモデルをさまざまなタスクで評価し、スコアを一覧で比べられるようにしたもので、Hugging Faceのリーダーボードで公開されています。
実際のモデルは、次のリーダーボードで調べられます。ページ上でMTEB(英語)とMMTEB(多言語)を切り替えられ、スコアだけでなく次元数・最大入力長・ライセンス・モデルの種類などで見比べられます。
https://huggingface.co/spaces/mteb/leaderboard
順位の高いものが自分のデータに対して最もよいとは限らないため、ランキング上位のものから候補をいくつか選んで、自分のデータと質問で検索の精度を測って決めるのがよいと思います。
まとめ
embeddingがどういうものであり、意味の近さをどのように数値で測るかを、既存のモデルを実際に動かしながら確かめました。要点を以下にまとめます。
- embeddingは、テキストを意味を表す数値ベクトルに変換したもので、その数値の個数を次元と呼びます。
- 一番大事な性質は「意味が近い文章どうしはベクトルも近くなる」ことです。
- ベクトルの近さは、コサイン類似度がよく使われます。正規化した場合は内積と一致します。
- クエリと文書は必ず同じモデルで埋め込みます。モデルを変えたら、保存済みの文書も埋め込み直す必要があります。
- モデル候補の絞り込みにはMTEB・MMTEBを使い、最終判断は自分のデータで行います。
参考資料
- sentence-transformers公式ドキュメント
https://www.sbert.net/ - 使用したモデル(Hugging Face)
https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 - OpenAI Embeddings(次元の調整・ベクトルの正規化について)
https://platform.openai.com/docs/guides/embeddings - MTEB/MMTEBリーダーボード(Hugging Face)
https://huggingface.co/spaces/mteb/leaderboard - MTEBリポジトリ
https://github.com/embeddings-benchmark/mteb
参考記事
- RAGとは何か:最小のRAGを作って動かす
https://libertytree-seek.com/contents/development/20534/