0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テキストの数値化はどう進化したか ― BoW・TF-IDFからword2vecまで

0
Last updated at Posted at 2026-07-03

この記事は、書籍『Kaggleではじめる大規模言語モデル入門』の輪読会で学んだテーマを、自分の言葉で整理・深掘りしたものです。

はじめに

シリーズ第1回で、NLP のコアの問題は「テキストをどう数値に変換するか」だと書きました。
この記事では、その数値化手法が深層学習以前にどう発展してきたかを、

BoW → n-gram → TF-IDF → 単語文脈行列 / PPMI → 次元圧縮 → word2vec

という一本の流れで追います。

「前の手法の何が問題で、次の手法はそれをどう解決したか」を意識して読むと、単なる手法一覧ではなく進化のストーリーとして頭に入ります。
古い手法ではあるものの、TF-IDF + 線形モデルは今でも強力なベースラインであり、Kaggle でも普通に登場します。

出発点:one-hot 表現の限界

最も素朴な単語の数値化は one-hot 表現です。
語彙の中で「その単語の位置だけ 1、他は 0」のベクトルにします。

りんご → [1, 0, 0, 0, ...]
バナナ → [0, 1, 0, 0, ...]
自動車 → [0, 0, 1, 0, ...]

しかしこれには本質的な欠陥があります。

  • どの2単語も内積が 0 :「りんご」と「バナナ」が似ていることを表現できない
  • 次元数 = 語彙数 :語彙が10万語あれば10万次元になる

「意味の近さをベクトルの近さで表せない」という問題が、この記事全体を貫くテーマになります。

Bag-of-Words(BoW):単語の出現回数で文書を表す

文書レベルの数値化として最初に登場するのが Bag-of-Words です。
文書を「単語の出現回数のベクトル」で表現します。
語順は無視して単語を袋(bag)に放り込むイメージです。

例として2つの文を語彙でカウントしてみます。

今日 カレー ラーメン 食べた
「今日はカレーを食べた」 1 1 1 0 1 1
「今日はラーメンを食べた」 1 1 0 1 1 1

各行がその文のベクトルになります。
scikit-learn なら数行です。

from sklearn.feature_extraction.text import CountVectorizer

docs = ["今日 は カレー を 食べた", "今日 は ラーメン を 食べた"]  # 分かち書き済み
vec = CountVectorizer()
X = vec.fit_transform(docs)
print(vec.get_feature_names_out())
print(X.toarray())

BoW の長所

  • 実装が単純で高速。そのまま機械学習モデルの入力にできる
  • 文書同士の類似度をコサイン類似度で測れる

BoW の短所

  1. 表記ゆれに弱い :「りんご」と「リンゴ」は完全に別の次元になる
  2. 日本語では分かち書きが前提 :単語境界が自明でないため、形態素解析が必要。「太陽光発電設備保全技術者」のような複合語をどこで切るかという問題もつきまとう
  3. 英語では活用形が別単語扱いrun / runs / running が別次元になる。レンマ化・ステミングで正規化できるが、時制などの情報は失われる
  4. 高次元・スパース :大規模コーパスでは語彙が数十万になり、ベクトルはほとんどが 0 の疎な表現になる。しかも「は」「の」「is」のような機能語の頻度ばかり大きく、情報量が乏しい
  5. 語順を完全に捨てている :これが最大の問題

語順の喪失がどれほど致命的かは、次の例でわかります。

「私は彼が嫌い」と「彼は私が嫌い」

意味は正反対ですが、単語の構成は同一なので BoW ではまったく同じベクトル になります。
否定表現がどの語に係るかも捉えられません。

n-gram:語順を部分的に取り戻す

連続する n 個の単語をひとまとまり(トークン)として扱うのが 単語 n-gram です。
「私 は 彼 が 嫌い」の単語 2-gram は「私は」「は彼」「彼が」「が嫌い」となり、先ほどの2文は異なるベクトルになります。
局所的な語順を回復できる一方、n を大きくすると次元爆発とスパース化がさらに深刻になるトレードオフがあります(実務では 1〜2 gram の併用が多い)。

TF-IDF:「その文書らしい単語」を重視する

BoW の「機能語の頻度ばかり大きい」問題への処方箋が TF-IDF です。
単語 $w$ の文書 $d$ における重みを次で定義します。

\text{tf-idf}(w,d) = \underbrace{\text{tf}(w,d)}_{\text{文書 } d \text{ 内での頻度}} \times \underbrace{\log\frac{N}{\text{df}(w)}}_{\text{IDF:単語の希少さ}}
  • TF(Term Frequency) :その文書内でよく出る単語ほど大きい
  • IDF(Inverse Document Frequency) :$N$ は全文書数、$\text{df}(w)$ は単語 $w$ を含む文書数。 多くの文書に出る単語ほど小さくなる

直感を数値で確認します。
全1,000文書のコーパスで、ある文書に「は」が10回、「深層学習」が5回出たとします。

単語 TF df IDF = log(1000/df) TF-IDF
10 1,000 log(1) = 0 0
深層学習 5 20 log(50) ≈ 3.9 19.6

全文書に現れる「は」の重みは 0 になり、一部の文書にしか出ない「深層学習」が強調されます。
頻度は半分なのに重みは圧倒的に大きい── 「どの文書にもある語を抑え、その文書を特徴づける語を引き立てる」 のが TF-IDF の効果です。

from sklearn.feature_extraction.text import TfidfVectorizer

vec = TfidfVectorizer()
X = vec.fit_transform(docs)

TF-IDF + ロジスティック回帰(や LightGBM)は、今でもテキスト分類の最初のベースラインとして優秀です。
ただし、高次元スパース・語順喪失・表記ゆれといった BoW 由来の問題は残ったままです。

単語文脈行列:分布仮説で「単語」をベクトル化する

ここで視点を文書から 単語 に移します。
手がかりになるのが 分布仮説 ──「単語の意味は、その周辺に現れる単語の分布で決まる」という考え方です。

「コーヒー」と「紅茶」はどちらも「飲む」「カップ」「淹れる」の近くに現れやすい。
周辺語の分布が似ているなら、意味も近いはずだ、という発想です。
ある単語の周辺(ウィンドウ内)に別の単語が現れることを 共起 と呼びます。

共起の回数を行列にまとめたものが 単語文脈行列 です。
行が単語、列が文脈語、要素が共起回数です。

飲む 走る 吠える カップ 散歩
コーヒー 12 0 0 8 0
紅茶 10 0 0 9 0
1 5 7 0 9

各行をその単語のベクトルとみなすと、「コーヒー」と「紅茶」の行は似ており、「犬」とは似ていません。
one-hot ではできなかった 意味の近さの表現 が、共起情報によって可能になりました。

PPMI:頻出語の影響を補正する

生の共起回数では、「は」「の」のような頻出語との共起がどの単語でも大きくなり、意味の情報が埋もれます。
そこで「単語 $x$ と $y$ が、偶然を超えてどれだけ一緒に出やすいか」に変換したのが PPMI(正の相互情報量) です。

\mathrm{PPMI}(x,y) = \max\left(0,\; \log\frac{P(x,y)}{P(x)P(y)}\right)

$P(x)P(y)$ は「2語が独立ならこの確率で共起するはず」という期待値で、実際の共起確率 $P(x,y)$ がそれを上回るほど値が大きくなります。
頻出語はそもそも $P(y)$ が大きいため、分母が効いて重みが抑えられる仕組みです。

それでも残る問題が 超高次元・スパース です。
語彙が10万語なら 10万×10万 の行列になり、要素のほとんどは 0 です。

次元圧縮:疎な高次元を密な低次元へ

高次元スパースなベクトルを、情報をなるべく保ったまま 低次元の密なベクトル に圧縮します。
代表的な手法は次のとおりです。

  • LSA(潜在的意味解析) :単語文脈行列を行列分解し、背後の潜在的な意味構造を抽出する枠組み
  • SVD(特異値分解) :LSA で使われる代表的な分解。$X = U\Sigma V^T$ と分解し、大きな特異値に対応する成分だけを残して低次元化する($U$ が単語ベクトル、$\Sigma$ が各成分の重要度に対応)
  • PCA(主成分分析) :データの分散が最大になる方向から順に軸を取り直す。SVD と数学的に密接な関係がある
  • ICA(独立成分分析) :PCA が「無相関」な成分を求めるのに対し、統計的に「独立」な成分を求める

こうして得られる低次元・密のベクトルを 分散表現 (あるいは 埋め込み / Embedding )と呼びます。
意味的に近い単語が近いベクトルに配置される、というのが分散表現の本質的な価値です。

word2vec:予測タスクで分散表現を学習する

「共起行列を作ってから分解する」のではなく、 ニューラルネットに予測タスクを解かせ、その副産物として分散表現を得る というアプローチが word2vec(2013)です。
学習方式は2つあります。

方式 解かせるタスク
CBOW 周辺語から中心語を予測する(「毎朝 ___ を飲む」の穴埋め)
Skip-gram 中心語から周辺語を予測する(「コーヒー」の周りに来る語は?)

穴埋め問題を正しく解くには、モデルは「コーヒーと紅茶は同じ文脈に入る」ことを学ばざるを得ません。
その知識が重み行列=単語ベクトルに蓄積される、という仕掛けです。
分布仮説を「行列の集計」ではなく「学習」で実現したものと言えます。

ベクトルの足し引きが意味の演算になる

word2vec の有名な性質が、単語ベクトルの加減算が意味の操作に対応することです。

\text{king} - \text{man} + \text{woman} \approx \text{queen}

word2vec-analogy.png

青い矢印(man→king、woman→queen)が「王族」の方向、オレンジの矢印(man→woman)が「性別」の方向に対応しており、king から性別の方向へ移動すると queen のすぐ近くに着地します。
「王様から男性性を引いて女性性を足すと女王になる」──性別・首都と国・活用形といった 単語間の関係が、ベクトル空間内の方向として埋め込まれる のです。
gensim を使うと簡単に試せます。

from gensim.models import KeyedVectors

# 学習済みモデル(例: 日本語Wikipediaで学習したもの)をロード
wv = KeyedVectors.load_word2vec_format("model.bin", binary=True)

wv.most_similar(positive=["", ""], negative=[""], topn=3)
wv.similarity("コーヒー", "紅茶")   # 高い
wv.similarity("コーヒー", "")     # 低い

word2vec の位置づけと限界

word2vec は「頻度の集計」から「学習ベースの埋め込み」への転換点であり、以降の NLP は埋め込みを学習する方向へ一気に進みます。
ただし word2vec にも限界があります。
1つの単語に1つの固定ベクトル しか割り当てられないため、「銀行の bank」と「土手の bank」を区別できません。
この多義性の問題を「文脈に応じてベクトルを変える」ことで解決したのが BERT 以降の文脈化埋め込みで、現在の LLM に直結していきます。

まとめ

数値化手法の進化を「問題 → 解決」の連鎖で振り返ります。

手法 解決したこと 残った問題
one-hot 単語を一応ベクトルにできる 意味の近さを表せない・高次元
BoW 文書をベクトル化し、ML に投入可能に 語順喪失・機能語が支配的・スパース
n-gram 局所的な語順を回復 次元爆発が悪化
TF-IDF 文書を特徴づける語を強調 語順喪失・スパースは未解決
単語文脈行列 + PPMI 分布仮説で単語の意味の近さを表現 超高次元・スパース
次元圧縮(SVD等) 低次元の密な分散表現を獲得 大規模コーパスでの計算コスト
word2vec 予測タスクによる学習で分散表現を獲得。意味の演算が可能に 多義語を扱えない(→ BERT 以降へ)

この「何が問題で、どう解決されたか」の連鎖を押さえておくと、Transformer や LLM の埋め込みを学ぶときにも「あの問題をこう解決したのか」という視点で理解できるようになります。

参考

シリーズの他の記事

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?