12. テキストの意味を捉える Embedding¶
第 12 回では、Embedding(埋め込み) の考え方を学び、Word2Vec・Transformer・BERT といった現代的な手法を紹介します。後半では教員の PC で動かしているローカル LLM サーバ(LM Studio)に HTTP でアクセスし、類似文書検索・文書分類を実装します(予定)。
12.1 BoW / TF-IDF の限界¶
12.1.1 単語の意味を考慮できない¶
第 11 回で学んだ Bag-of-Words や TF-IDF は、シンプルで実用的な手法ですが、いくつかの限界があります。その一つは、単語の意味を考慮できない点です。
例 1: 類似した意味の単語
BoW / TF-IDF では、「犬」「猫」「車」は全く別の単語として扱われます。しかし、意味的には「犬」と「猫」は「動物」として近い関係にあります。
例 2: 同義語・類義語
「面白い」と「楽しい」は似た意味ですが、BoW / TF-IDF では全く異なる単語として扱われ、類似度が低くなってしまいます。
12.1.2 文脈を考慮できない¶
第 10 回で触れた例を思い出してみましょう。
この文で「大きい」のは「目」でしょうか、「猫」でしょうか?BoW / TF-IDF では、このような文脈による意味の違いを区別できません。
12.1.3 スパース(疎)なベクトル¶
BoW / TF-IDF では、語彙数が増えるとベクトルの次元数も増加します。しかし、各文書には語彙の一部しか出現しないため、ほとんどの要素が 0 になります(スパースなベクトル)。
これらの問題を解決するために、単語の意味を考慮したベクトル化手法 が開発されました。
12.2 Embedding(埋め込み)¶
12.2.1 Embedding の概念¶
Embedding(埋め込み) とは、単語やテキストを固定長の密なベクトルに変換する手法です。「埋め込み」という名前は、高次元の離散的なデータ(単語)を、低次元の連続的なベクトル空間に「埋め込む」ことに由来します。
BoW / TF-IDF との違い:
| 特徴 | BoW / TF-IDF | Embedding |
|---|---|---|
| ベクトルの種類 | スパース(疎) | 密(Dense) |
| 次元数 | 語彙数に依存(数千〜数万) | 固定(数百程度) |
| 意味の表現 | できない | できる |
| 類義語の扱い | 別々のベクトル | 近いベクトル |
12.2.2 なぜ Embedding が有効か¶
Embedding では、意味的に近い単語やテキストが、ベクトル空間上でも近くに配置されます。
たとえば、Multimodal Embedding Models - Weaviate の Figure 1 のように、「犬」と「猫」は近く、「バナナ」と「リンゴ」も近い位置に配置されます。これにより、単語の意味的な関係を数値で扱えるようになります。
12.3 Word2Vec¶
12.3.1 Word2Vec とは¶
Word2Vec は、単語の Embedding を学習する手法です。2013 年に Google の研究者によって提案されました。
特徴
- 似た文脈で使われる単語は、似たベクトルになる
- 固定長の密なベクトル(例:300 次元)で表現
- 単語の意味の演算ができる
有名な例
「王」から「男性」の意味を引き、「女性」の意味を足すと、「女王」に近いベクトルになります。
12.3.2 Word2Vec の仕組み(概要)¶
Word2Vec は、大量のテキストデータから「単語の周辺にどんな単語が出現するか」を学習します。
同じような文脈で使われる単語(例:「犬」と「猫」)は、似たベクトルになります。これは 「分布仮説」(似た文脈に出現する単語は似た意味を持つ)に基づいています。
12.3.3 Word2Vec の限界¶
Word2Vec にも限界があります。
- 単語単位: テキスト全体の意味を直接表現できない
- 文脈を考慮しない: 同じ単語は常に同じベクトル(多義語の区別ができない)
- 未知語に対応できない: 学習データにない単語はベクトル化できない
12.4 Transformer と BERT¶
12.4.1 Transformer とは¶
Transformer は、2017 年に Google が発表した深層学習モデルのアーキテクチャです。現在の自然言語処理の基盤技術となっています。
Transformer の特徴:
- Attention(注意機構): 文中の重要な部分に「注意」を向けて処理
- 並列処理: 文全体を一度に処理できるため高速
- 長距離依存: 文の離れた位置にある単語同士の関係も捉えられる
12.4.2 BERT とは¶
BERT(Bidirectional Encoder Representations from Transformers) は、2018 年に Google が発表した言語モデルです。Transformer をベースに、大量のテキストデータで事前学習されています。
BERT の特徴
- 双方向: 文の前後両方の文脈を考慮
- 文脈依存: 同じ単語でも文脈によって異なるベクトルになる
- 事前学習: 大量のデータで学習済みなので、少ないデータでも高精度
Word2Vec との違い
| 特徴 | Word2Vec | BERT |
|---|---|---|
| 単位 | 単語 | 単語・文 |
| 文脈 | 考慮しない | 考慮する |
| 多義語 | 区別できない | 区別できる |
| モデルサイズ | 小さい | 大きい |
12.4.3 事前学習と転移学習¶
BERT などのモデルは、以下の 2 段階で利用されます。
- 事前学習(Pre-training): 大量のテキストデータで言語の一般的な知識を学習
- ファインチューニング(Fine-tuning): 特定のタスク(感情分析など)用に追加学習
私たちは、すでに事前学習されたモデルを使うことで、少ないデータでも高精度な分類が可能になります。これを 転移学習 と呼びます。
12.5 ローカル LLM サーバによる Embedding¶
12.5.1 ローカル LLM サーバを使う理由¶
Embedding を得るために Sentence Transformers などのライブラリを学生個別の PC にインストールすると、PyTorch(約 500 MB)+ 事前学習済みモデル(数百 MB)+ GPU 設定など、環境構築だけで一仕事になります。
本講義では代わりに、教員が運用する 1 台の LLM サーバ(LM Studio)に学生全員が HTTP でアクセスする形を取ります。学生側は requests ライブラリで通信するだけで、重いモデルのダウンロードや GPU セットアップは一切不要です。
この方式のメリット
- ローカルインストール不要(PyTorch などの環境構築が不要)
- API キー管理が不要(外部クラウド API のような課金もなし)
- 教員 PC の GPU を学生全員で共有
- 第 13 回でも同じサーバを「チャットモデル」用に再利用する
- API が OpenAI 互換なので、OpenAI などの外部 API(付録C)とほぼ同じ書き方で呼び出せる
12.5.2 サーバの接続情報¶
| 項目 | 内容 |
|---|---|
| サーバ URL | 授業でアナウンスされる URL(https://~.trycloudflare.com の形式) |
| Embedding モデル | text-embedding-bge-m3 (多言語対応・1024 次元) |
| API エンドポイント | POST /v1/embeddings (OpenAI 互換) |
サーバ URL は毎回変わります
教員サーバは Cloudflare トンネル経由で公開しており、URL は授業のたびに変わります。毎回の授業でアナウンスされる https://~.trycloudflare.com を、コード中の LLM_URL に設定してください(末尾にポート番号 :1234 は付けません)。
サーバが使えない場合は自分の PC で LM Studio を起動
講義期間外などで上記サーバが停止している場合や、自宅環境で実験したい場合は、LM Studio を自分の PC にインストールすれば同じことができます。
- LM Studio を起動し、検索から
bge-m3(Embedding モデル)をダウンロード - 左側の「Developer」タブでサーバを起動(デフォルトで
http://localhost:1234) - ダウンロードした Embedding モデルをロード
その上でコード中の LLM_URL を http://localhost:1234 に変更してください。
12.5.3 ベクトル化の基本¶
Python 標準的な requests ライブラリで LM Studio の Embedding API を呼び出します。複数文をまとめてベクトル化したいので、ヘルパー関数 encode() を用意しておくと以降のコードがすっきりします。
import requests
import numpy as np
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
# テキストを Embedding ベクトルに変換
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
# 文のリスト
sentences = [
"私は機械学習が好きです",
"私は深層学習が好きです",
"今日は天気が良いです"
]
# 文をベクトルに変換
embeddings = encode(sentences)
print(f"ベクトルの形状: {embeddings.shape}")
print(f"1文目のベクトル(最初の10要素): {embeddings[0][:10]}")
ベクトルの形状: (3, 1024)
1文目のベクトル(最初の10要素): [-0.013 0.024 -0.045 0.011 0.038 -0.019 0.007 -0.022 0.041 -0.005]
各文が 1024 次元の密なベクトル に変換されました。
LM Studio の Embedding API について
LM Studio の /v1/embeddings は OpenAI 互換で、input に文字列のリストを渡せば複数文を 1 リクエストでまとめてベクトル化できます。レスポンスの data 配列に、入力と同じ順序でベクトルが入って返ってきます。
12.5.4 使用するモデル¶
本講義では Embedding モデルとして bge-m3 を使用します。
| 項目 | 内容 |
|---|---|
| モデル名 | bge-m3(LM Studio 上の ID は text-embedding-bge-m3) |
| 開発元 | BAAI(北京智源人工知能研究院) |
| 対応言語 | 100 以上の言語(日本語を含む) |
| ベクトル次元 | 1024 次元 |
| 特徴 | 多言語対応・高精度・長文(最大 8192 トークン)対応 |
このモデルは Hugging Face で公開されており、LM Studio からダウンロードして利用します。
他の Embedding モデル
LM Studio では他にもさまざまな Embedding モデルが利用できます。サーバ側でモデルがロードされていれば、EMBED_MODEL の名前を変えるだけで切り替え可能です(/v1/models で利用可能なモデル一覧を確認できます)。
| モデル | 言語 | 次元 | 特徴 |
|---|---|---|---|
text-embedding-bge-m3 |
多言語 | 1024 | 日本語対応・本講義で使用 |
text-embedding-nomic-embed-text-v1.5 |
英語中心 | 768 | 軽量 |
12.6 コサイン類似度による類似文書検索¶
12.6.1 コサイン類似度とは¶
コサイン類似度(Cosine Similarity) は、2 つのベクトルがなす角度のコサイン値を使って類似度を測る方法です。
コサイン類似度の特徴:
- 値の範囲: -1 から 1
- 1 に近い: 非常に似ている
- 0 に近い: 似ていない
- -1 に近い: 正反対の意味
12.6.2 類似度の計算¶
12.5 節で作った encode() 関数で得たベクトルを使って、文間の類似度を計算してみましょう。
コサイン類似度を計算するには、scikit-learn の cosine_similarity 関数を使用します。ベクトルの配列を受け取り、全ペアの類似度を含む行列を返します。
import requests
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
# 文のリスト
sentences = [
"私は機械学習が好きです",
"私は深層学習が好きです",
"今日は天気が良いです",
"機械学習と深層学習を勉強しています"
]
# ベクトル化
embeddings = encode(sentences)
# コサイン類似度を計算
similarity_matrix = cosine_similarity(embeddings)
print("類似度行列:")
for i, sent in enumerate(sentences):
print(f"\n文{i}: {sent}")
for j, sim in enumerate(similarity_matrix[i]):
print(f" vs 文{j}: {sim:.3f}")
類似度行列:
文0: 私は機械学習が好きです
vs 文0: 1.000
vs 文1: 0.860
vs 文2: 0.606
vs 文3: 0.756
文1: 私は深層学習が好きです
vs 文0: 0.860
vs 文1: 1.000
vs 文2: 0.521
vs 文3: 0.795
文2: 今日は天気が良いです
vs 文0: 0.606
vs 文1: 0.521
vs 文2: 1.000
vs 文3: 0.523
文3: 機械学習と深層学習を勉強しています
vs 文0: 0.756
vs 文1: 0.795
vs 文2: 0.523
vs 文3: 1.000
「機械学習」「深層学習」を含む 3 つの文(文 0・1・3)は互いに 0.75〜0.86 と高い類似度を示し、トピックが異なる文 2(天気)とは 0.52〜0.61 程度と低めの値になっています。bge-m3 は XLM-RoBERTa をベースにした Transformer(BERT 系)のモデルで、「意味が近い文同士はベクトル空間でも近い」という性質を持っているため、コサイン類似度で意味的な近さを測れます(具体的な数値はモデルのバージョンや量子化により多少変動します)。
12.7 ローカル LLM サーバによる文書分類¶
12.7.1 分類の流れ¶
Embedding を特徴量に使った文書分類は、以下の流れで行います。
- 文を Embedding でベクトル化(LM Studio 経由)
- ベクトルを特徴量として分類器(ロジスティック回帰など)に入力
- 分類モデルを学習・予測
BoW や TF-IDF と違って、「意味的に近い文」が特徴量空間でも近くに配置される ため、少ない訓練データでも高い精度が出やすいのが Embedding ベースの分類の強みです。
12.7.2 感情分析の実装¶
映画レビューデータ movie_reviews.csv を使って、ポジティブ/ネガティブの 2 クラス分類を実装します。
import requests
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
# データの読み込み
df = pd.read_csv('movie_reviews.csv')
print(f"データ数: {len(df)}")
print(df['label'].value_counts())
# 文をまとめてベクトル化(200 文を 1 リクエストで送ります)
print("\nベクトル化中...")
embeddings = encode(df['text'].tolist())
print(f"ベクトルの形状: {embeddings.shape}")
# ラベルを数値に変換
df['label_num'] = (df['label'] == 'positive').astype(int)
# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(
embeddings, df['label_num'],
test_size=0.2,
random_state=42
)
# ロジスティック回帰で学習
clf = LogisticRegression(random_state=42, max_iter=1000)
clf.fit(X_train, y_train)
# 予測
y_pred = clf.predict(X_test)
# 評価
accuracy = accuracy_score(y_test, y_pred)
print(f"\n精度: {accuracy:.2%}")
print("\n分類レポート:")
print(classification_report(y_test, y_pred, target_names=['Negative', 'Positive']))
データ数: 200
label
positive 100
negative 100
Name: count, dtype: int64
ベクトル化中...
ベクトルの形状: (200, 1024)
精度: 100.00%
分類レポート:
precision recall f1-score support
Negative 1.00 1.00 1.00 19
Positive 1.00 1.00 1.00 21
accuracy 1.00 40
macro avg 1.00 1.00 1.00 40
weighted avg 1.00 1.00 1.00 40
200 文のレビューを bge-m3 で 1024 次元のベクトルに変換し、その上でロジスティック回帰を回すだけで、この例ではテストデータ 40 件を全問正解(100%)できました。BoW や TF-IDF と比べて、「意味が似ているレビュー同士は似たベクトルになる」という Embedding の特性が効いています。
100% を鵜呑みにしない
ここでの 100% は「Embedding が強力」なことに加えて、「データが 200 件と小さく、テストも 40 件しかない」ことによる面が大きいです。テストが少ないと、たまたま全問正解することは珍しくありません。実務では、より多くのデータ・交差検証(cross-validation)で評価し、精度が偶然でないかを確かめます。
ベクトル化の所要時間とキャッシュ
LM Studio の /v1/embeddings は複数文をまとめて送ることができますが、200 文分の推論には GPU でも数秒かかります。実務では同じテキストを何度もベクトル化することは避けたいので、得られた embeddings を numpy.save などでキャッシュしておくと良いです。
12.8 演習問題¶
演習 12-1: 意味が近い文を探す¶
演習 12-1(初級)
1 つのクエリ文に対して、候補文のリストの中から意味が最も近い文を探すプログラム(類似文検索)を作成してください。Embedding とコサイン類似度を使うと、表記が違っても意味が近い文を見つけられます。
タスク
queryとcandidatesをまとめてencode()でベクトル化するcosine_similarityで全文どうしの類似度行列を計算する- クエリと各候補の類似度を表示し、最も類似度が高い文を出力する
import requests
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
query = "スマートフォンの新製品が発表された"
candidates = [
"新しいスマホが登場して話題になっている",
"今日の夕食はカレーライスにした",
"台風が接近し、明日は大雨の予報だ",
"最新の携帯電話が公開され注目を集めた",
]
# 1. クエリと候補をまとめてベクトル化する
# 2. コサイン類似度の行列を計算する(cosine_similarity)
# 3. クエリと各候補の類似度を表示し、最も類似度が高い文を出力する
演習 12-2: Embedding で感情を判定する¶
演習 12-2(初級)
学習をせずに、ポジティブ/ネガティブを代表する 2 つの「アンカー文」との類似度だけで、文の感情を判定してみましょう。判定したい文を 2 つのアンカーと比べ、近い方のラベルを付けます。
タスク
- アンカー文(
pos_anchor・neg_anchor)とテスト文をまとめてencode()でベクトル化する cosine_similarityで全文どうしの類似度行列を計算する- 各テスト文について、pos アンカーとの類似度が neg アンカーより高ければ
positive、そうでなければnegativeと判定して表示する
import requests
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
# ポジ・ネガを代表するアンカー文
pos_anchor = "とても素晴らしくて大満足の最高の内容だった"
neg_anchor = "ひどい出来でがっかりする最悪の内容だった"
# 判定したい文
tests = [
"期待以上の出来で本当に感動した",
"時間の無駄だった。二度と見たくない",
"話の展開が退屈で全く楽しめなかった",
"映像が美しく、心から楽しめる作品だった",
]
# 1. アンカー文とテスト文をまとめてベクトル化する
# 2. コサイン類似度の行列を計算する(cosine_similarity)
# 3. 各テスト文について pos/neg アンカーとの類似度を比べ、近い方のラベルを表示する
演習 12-3: TF-IDF と Embedding の精度比較(メール 3 クラス分類)¶
演習 12-3(中級)
第 11 回の課題で使った emails.csv(ビジネス・プライベート・スパムの 3 クラス、各 100 件・計 300 件)を使って、第 11 回の TF-IDF と本章の bge-m3 Embedding でメール分類モデルを作り、テスト精度を比較してください。ベクトル化の方法だけを変え、分類器(ロジスティック回帰)と分割条件は揃えます。どちらが高精度か、なぜかも考えてみましょう。
タスク
- CSV を読み込む(ラベルは
business・private・spamの 3 種類。文字列のまま使ってよい) - TF-IDF 版:
MeCab.Tagger("-Owakati")で分かち書き →TfidfVectorizer→ ロジスティック回帰で精度を求める - Embedding 版:
encode()で bge-m3 ベクトル化 → ロジスティック回帰で精度を求める - 2 つの精度を並べて表示し、どちらが高いか・なぜかをコメントで考察する
※ 公平に比べるため、先に train_test_split(df, test_size=0.2, random_state=42, stratify=df['label']) で 1 回だけ訓練・テストに分割し、同じ行を両手法で使ってください。
import requests
import numpy as np
import pandas as pd
import MeCab
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
LLM_URL = "https://xxxxxxxx.trycloudflare.com" # 授業でアナウンスされる URL に置き換える
EMBED_MODEL = "text-embedding-bge-m3"
def encode(texts):
if isinstance(texts, str):
texts = [texts]
response = requests.post(
f"{LLM_URL}/v1/embeddings",
json={"model": EMBED_MODEL, "input": texts}
)
data = response.json()["data"]
return np.array([d["embedding"] for d in data])
# 1. データ読み込み(ラベルは文字列のまま使う)
df = pd.read_csv('emails.csv')
# 2. 先に 1 回だけ train_test_split で分割
# 3. TF-IDF + ロジスティック回帰で精度を求める
# 4. bge-m3 Embedding + ロジスティック回帰で精度を求める
# 5. 2 つの精度を並べて表示・考察
12.9 まとめ¶
本章では、単語・テキストの意味を捉えるベクトル化手法について学びました。
第 12 回のまとめ
BoW / TF-IDF の限界
- 単語の意味を考慮できない
- 文脈を考慮できない
- スパースなベクトル
Embedding(埋め込み)
- 単語やテキストを固定長の密なベクトルに変換
- 意味的に近いものは、ベクトル空間上でも近くに配置
Word2Vec
- 単語の意味をベクトル化
- 似た文脈の単語 → 似たベクトル
- 単語の演算が可能(king - man + woman ≈ queen)
Transformer / BERT
- Attention 機構により文脈を考慮
- 事前学習 + ファインチューニングで高精度
ローカル LLM サーバ経由の Embedding
- 文全体を一つの密ベクトルに変換
- 教員 PC で動く LM Studio サーバ(OpenAI 互換 API)を HTTP で呼び出すだけ
- 学生側はライブラリインストール不要
- 本講義では
bge-m3(多言語対応・1024 次元)を使用
コサイン類似度
- ベクトル間の角度で類似度を測る
- 1 に近いほど類似
自然言語処理(第 10 〜 12 回)の総まとめ
第 10 回:自然言語処理の基礎
- テキストクリーニング(URL、記号の削除)
- 形態素解析(MeCab)
- 正規化(ストップワード除去、品詞フィルタリング)
第 11 回:テキストデータを用いた機械学習
- Bag-of-Words(単語の出現回数)
- TF-IDF(単語の重要度を考慮)
- ナイーブベイズによる文書分類
第 12 回:テキストの意味を捉える Embedding
- Embedding(埋め込み)の概念
- Word2Vec(単語の意味をベクトル化)
- Transformer / BERT(文脈を考慮)
- ローカル LLM サーバ経由の Embedding(文全体をベクトル化)