論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
TF-IDF
Term Frequency-Inverse Document Frequency
NLP

🔖 キーワード索引

「TF-IDF」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。

TF-IDF単語重要度BoW文書ベクトルIDFコサイン類似度検索テキストマイニング

💡 30秒で分かる結論 — TF-IDF

🍰 まずはやさしく

単語の重要さを測る物差しです。

文章の特徴を数字で表すために使います。

スマホの検索などでキーワードを探します。

まずは結論を短くまとめます。

最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:

📍 文脈 — どこで出会うか

🍰 まずはやさしく

文章を数字の列に変える方法です。

記事の内容を自動で分けるために使います。

ニュースのジャンル分けに役立ちます。

どんな場面で使うかを説明します。

「ニュース記事を分類したい」 「論文の特徴的キーワードを取り出したい」 — テキストを数値ベクトルに変える最も古典的かつ強力な方法が TF-IDF。 scikit-learn の TfidfVectorizer 一発で動きます。

このページの読み方:まず 30秒結論 と 直感 を読み、 必要に応じて 数式 や 計算例、 落とし穴 に進んでください。

🎨 直感で掴む

🍰 まずはやさしく

レアな単語を大切にする考え方です。

その文章らしさを判断するために使います。

料理のレシピで珍しい材料を探す感じです。

直感的にわかる例で解説します。

10 件のレシピがあり、 そのうち 1 件のレシピに「ターメリック」が 5 回出てくるとします。

一方「塩」は全 10 件に出てくるので IDF ≈ log(10/10) = 0。 TF×IDF も 0 → 全体共通の単語は自動的に重み 0 に。

🎨 概念図で押さえる — TF-IDF の3つの視点

TF-IDF の理解には、 (1) 単語頻度の分布形 (Zipf 則の確認)、 (2) 文書間の類似度パターン (散布図)、 (3) 用語-文書行列のヒートマップ表現 の 3 点を同時に把握する必要があります。 ここでは SSDSE-B-2026 から作った 2 種類の小さなコーパス (指標名 109 個、 47 都道府県のプロフィール文書) で、 この 3 点を実際に描いてみます。

SSDSE-B-2026 の指標名 109 個の語の順位と出現回数の両対数プロットと、上位 12 語の IDF
図A: SSDSE-B-2026 の指標名 109 個 (「転入者数(日本人移動者)(男)」など) を 1 個 = 1 文書とし、 janome で分かち書きして記号・助詞を除いた語の頻度。 異なり 134 語・延べ 502 語で、 順位と回数を両対数で描くと右下がり (傾き −0.89) になり、 Zipf 則に近い形が小さなコーパスでも現れる。 最頻の「数」だけで延べ語数の 12.2%、 上位 10% の 13 語で 41.4% を占め、 1 回しか出ない語が 51 語ある。 IDF (smooth_idf、 ln((1+N)/(1+df)) + 1) は 61 文書に出る「数」で 1.57 と最小、 「者」2.76、 「人口」2.93 と続き、 1 文書にしか出ない「婚姻」「特殊」などは 5.01 になる。 TF-IDF はこうして、 どの指標名にも付く「数」のような語の重みを下げ、 その指標に固有の語の重みを上げる。

読み取るポイント:

  • 分布が右下がり → Zipf 則に近い、 少数の高頻度語 (ここでは「数」) の重みを IDF が下げる
  • 裾の長さで stopwords の影響を判定
  • 頻度上位の単語を確認すると、 ドメイン固有の偏りに気づける
47 都道府県のプロフィール文書の TF-IDF を 2 次元に縮約した散布図、数値ごと 1 語の文書と 3 分位の語の文書の比較
図B: 文書を TF-IDF ベクトルに変換し、 PCA (中心化してからの SVD) で 2 次元にした散布図。 左はページのコード 1 と同じ 47 都道府県 (2023 年度) の文書 (「東京都 人口1408万 出生863百 死亡1372百 高齢化22」など) で、 数値ごと 1 語になるため県どうしの類似度が 0 でないペアは 1,081 ペア中 95 だけ。 2 成分の寄与率は計 7.4% しかなく、 東京都・沖縄県・秋田県など 14 県が原点近くのほぼ同じ点に重なって、 群は見えない。 右は総人口・高齢化率・合計特殊出生率・年平均気温・1 人当たり延べ宿泊者数・消費支出の 6 指標を 3 分位で「人口_高」「高齢化_低」のような語にした文書で、 類似度が 0 でないペアは 947 に増え、 寄与率は 20.5% + 13.2%。 第 1 成分は「人口_高・高齢化_低・支出_高」(東京都 +0.64) と「出生率_高・高齢化_高・支出_低」の対比で、 総人口の多い県 (青) が右、 少ない県 (橙) が左に分かれる。 第 2 成分は「気温_低・出生率_低」が上 (北海道・秋田県) に来る。

読み取るポイント:

  • クラスタが分離 → トピックモデリングが有効
  • 線形に並ぶ → 単一軸 (都市規模など) で説明可能
  • 多数の点が 1 か所に重なる → 文書どうしが語を共有していない。 トークン化 (数値のビン化など) を見直す
3 分位の語 18 個の TF-IDF 値の、47 都道府県にわたる相関ヒートマップ
図C: 図 B 右の文書 (47 都道府県 × 3 分位の語 18 個の TF-IDF 行列) で、 語と語の TF-IDF 値の相関を 47 県にわたって計算したヒートマップ。 同じ文書に一緒に出やすい語のペアは赤、 一緒に出にくいペアは青になる。 同じ指標の「低・中・高」は 1 県に 1 つしか出ないので対角ブロックは青 (人口_低 × 人口_高 = −0.52)。 指標をまたぐと、 「人口_高 × 高齢化_低」0.62、 「人口_低 × 高齢化_高」0.43、 「出生率_低 × 支出_高」0.40 が赤く、 「人口_高 × 出生率_高」−0.49、 「人口_高 × 高齢化_高」−0.42 が青い。 宿泊の 3 語は他の指標の語とほとんど相関しない。

読み取るポイント:

  • 赤いセル → 同じ文書に一緒に出やすい語 (ここでは「大都市」を表す語どうし)
  • 青いセル → 一緒に出にくい語、 文書分類の手がかり
  • ブロックが不明瞭 → コーパスが均質、 別の特徴量が必要

この 3 枚を順に追うと、 「単語分布の確認 → 文書構造の可視化 → 共起パターンの抽出」 という TF-IDF 解析の標準フローが体系化される。 関連用語 コサイン類似度 / 情報エントロピー / エンベディング へ進むと、 各図の理論背景を深掘りできる。

📝 理解度チェック(練習問題)

TF-IDF の核を本当に掴めたかを 6 問で確認する。 すべて 1 分以内で答えられる粒度。 「自分で」答えを口に出して言える状態を目指したい。

Q1. TF と IDF それぞれの役割を一言で

狙い:TF = 「その文書内で何回出てくるか」(多いほど重要)。 IDF = 「全文書のうち何%にしか出てこないか」(珍しいほど重要)。 掛け合わせて「珍しくて頻出」の単語を高得点にする。

Q2. 「は」「の」「で」のような助詞が TF-IDF で重みが低くなる理由

狙い:ほぼ全文書に出現するので IDF が極小(log(N/N) ≈ 0)になる。 TF がいくら高くても IDF で潰される。 これがストップワード除去の根拠でもある。

Q3. 文書 D に「人口」が 10 回、 全 47 文書中 5 文書に「人口」が出る。 TF-IDF は

狙い:TF = 10 (生カウント版)、 IDF = log(47/5) ≈ 2.24。 TF-IDF ≈ 22.4。 sklearn の TfidfVectorizer は smooth=True で log((1+N)/(1+df)) + 1 を使うので少し違う値になる。

Q4. TF-IDF と単語頻度 (Bag-of-Words) の最大の違い

狙い:BoW は文書内頻度のみ。 TF-IDF は文書間希少性(IDF)も加味するので、 「全文書に出る単語」を自動的にダウンウェイトできる。

Q5. TF-IDF と Word2Vec / BERT、 どう使い分けるか

狙い:TF-IDF は語順・文脈を見ない疎ベクトル、 高速・解釈可能、 文書分類のベースラインに最適。 Word2Vec/BERT は語義・文脈を捉える密ベクトル、 性能高いが計算重い。 まず TF-IDF で土台を作ってから埋め込みへ。

Q6. SSDSE-B-2026 の県別データに TF-IDF はどう適用できるか

狙い:そのままでは適用不可(テキストデータでない)。 ただし「県別観光案内文」「県別 Wikipedia 記事」を別途取得すれば、 県を文書として扱った TF-IDF 解析が可能。 「観光名物」など県を特徴づける固有語を抽出できる。

これらの問題に詰まった項目があれば、 「TF と IDF の定義」「単語分布の可視化」セクションへ戻ろう。 TF-IDF は古典的だが、 現代の NLP パイプラインでも前処理や検索エンジン (Elasticsearch) で現役の道具である。

📐 定義・数式

🍰 まずはやさしく

計算式で重要度を出す仕組みです。

正確なスコア(点数)を出すために使います。

部活の報告書から重要な言葉を選びます。

具体的な計算方法を学びます。

【TF (Term Frequency)】
$$\mathrm{TF}(t, d) = \frac{\text{単語 } t \text{ の文書 } d \text{ 内の出現回数}}{\text{文書 } d \text{ の総単語数}}$$
【IDF (Inverse Document Frequency)】
$$\mathrm{IDF}(t) = \log\frac{N}{|\{d : t \in d\}|}$$
$N$=総文書数、 分母=単語 $t$ を含む文書数
【TF-IDF】
$$\mathrm{TF\text{-}IDF}(t, d) = \mathrm{TF}(t, d) \times \mathrm{IDF}(t)$$

🔬 記号・要素の読み解き

$t$(term)
単語(あるいは n-gram)。
$d$(document)
1 つの文書(記事、 ツイート、 商品レビュー等)。
$N$
コーパス全体の文書数。
TF
「この文書で何回出たか」 — 多いほど局所的に重要。
IDF
「他の文書でも出るか?」 — 全文書に出る単語ほど 0 に近づく。
log の理由
レア度の効果を緩める。 「100 倍レア」が「指標 100 倍」では極端すぎるため。

🔬 数式を言葉で読み解く(詳細版)

「TF-IDF」は 文書中の単語の重要度を測る古典的指標。頻出だがどの文書にも出る単語にはペナルティ。 です。 ここでは定義式の各記号、 直感的意味、 SSDSE-B-2026 への当てはめを段階的に解きほぐします。

① TF-IDF の定義式

基本形
$$\text{tf-idf}(t, d) = \text{tf}(t, d) \cdot \text{idf}(t)$$
単語 $t$ の文書 $d$ における重要度。 局所頻度 × 全体希少度。
$\text{tf}(t, d)$
Term Frequency。 文書 $d$ における単語 $t$ の出現回数 (または相対頻度)。
$\text{idf}(t)$
Inverse Document Frequency。 $\log(N / \text{df}(t))$。 $N$ は総文書数、 $\text{df}(t)$ は $t$ を含む文書数。
$N / \text{df}(t)$
「全文書のうち何分の 1 がその単語を持つか」の逆数。 1 文書だけ→大、 全文書→1。
$\log$
頻度の対数化。 「100 文書中 1 文書出現」と「1000 文書中 10 文書出現」を等価視。

② TF の各種定義

定義式意味
素朴 (raw count)$f_{t,d}$単純出現回数
対数正規化$1 + \log f_{t,d}$頻度の歪み緩和
頻度正規化$f_{t,d} / \sum_{t'} f_{t',d}$文書長正規化
最大頻度正規化$0.5 + 0.5 \cdot f_{t,d} / \max_{t'} f_{t',d}$長文偏り抑制

③ IDF の各種定義

定義式
標準$\log(N / \text{df}(t))$
+1 平滑化 (sklearn)$\log((1+N) / (1+\text{df}(t))) + 1$
確率的 IDF$\log((N - \text{df}(t)) / \text{df}(t))$

④ TF-IDF ベクトル化と類似度

コサイン類似度
$$\text{cos}(d_1, d_2) = \frac{\mathbf{v}_1 \cdot \mathbf{v}_2}{\|\mathbf{v}_1\| \|\mathbf{v}_2\|}$$
各文書を TF-IDF ベクトル化し、 内積/ノルム積で類似度算出。 値域 [0, 1] (非負ベクトル)。

⑤ BM25 への発展

TF-IDF の現代版が BM25 (Best Match 25)。 単語頻度の飽和 (saturation) と文書長正規化を導入。 検索エンジン (Elasticsearch・Lucene) のデフォルト。 $\text{BM25}(t, d) = \text{IDF}(t) \cdot \frac{f_{t,d}(k_1 + 1)}{f_{t,d} + k_1(1 - b + b \cdot |d|/\text{avgdl})}$ で $k_1 \approx 1.2, b \approx 0.75$。

SSDSE-B-2026 で「TF-IDF」を体感する

SSDSE-B-2026 で「都道府県プロフィール文書」を TF-IDF 化。 入力は 47 都道府県を文書、 各都道府県の上位指標名(人口・出生・経済等)を単語に、 出力は scikit-learn TfidfVectorizer で重要指標を抽出。 47 都道府県 × 複数年の実データで具体計算を実施します。

🏭 産業界での活用事例(6 件)

業界事例役割SSDSE-B-2026 との対比
Google 検索初期ランキングで核となった指標クエリ-文書のマッチング都道府県名×指標のレポート検索
ElasticsearchBM25 (TF-IDF 改良版) を標準採用全文検索エンジンSSDSE 文書検索
Luceneオープンソース検索ライブラリSolr/ES の中核テキストデータベース
ニュース推薦Yahoo・Smartnews の特徴量TF-IDF + 行列分解都道府県別ニュース類似度
剽窃検出Turnitin・iThenticateTF-IDF ベクトル類似学術文書比較
カスタマーサポート FAQ マッチZendesk・Salesforce問い合わせと FAQ47 都道府県別問い合わせ

⚖️ 関連手法との比較表

手法定義特徴用途
TF-IDF古典統計ベース解釈容易・軽量中小規模検索
BM25TF-IDF の確率版長文ペナルティ追加現代検索エンジン標準
Word2Vec分布意味論・ベクトル類似語捕捉推薦・分類
BERT埋め込み文脈依存ベクトル文意理解可QA・分類・要約
LSA/LSITF-IDF + SVD潜在意味抽出トピック分析
LDA確率的トピックモデル文書の混合分布テーマ抽出
Sentence-BERT文埋め込み専用 BERT高速類似度意味検索

💥 失敗例から学ぶ

💥 ストップワード未除去
「の」「は」「が」が高 TF だが IDF も低い→無視される。 ただし日本語は分かち書きが必要。 MeCab + ストップワードリスト併用。
💥 文書長の偏り
長文書は単純 TF が高くなる。 頻度正規化または BM25 で補正。
💥 レアな typo が高 TF-IDF に
typo は 1 文書のみ出現で IDF 大、 TF も 1 で値が異常に高い。 min_df=2 などで除去。
💥 英大文字小文字
デフォルトの sklearn は lowercase 変換。 固有名詞 (Apple vs apple) で意味が変わる場合は注意。
💥 単語分割の質
日本語は MeCab・SudachiPy の辞書次第で「東京」「東京都」が別単語に。 一貫した分割が必須。

📝 演習問題(5 問・解答付き)

  1. 問題 1:SSDSE-B-2026 から「都道府県プロフィール文書」を作り、 TF-IDF で重要指標を抽出せよ。
    ▼ 解答
    📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) 北海道 5,092,000 24,430 東京都 14,086,000 86,348 沖縄県 1,468,000 12,549 …(全 47 行)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    import pandas as pd
    from sklearn.feature_extraction.text import TfidfVectorizer
    df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
    d = df[df['SSDSE-B-2026']==2023].copy()
    # 「都道府県プロフィール」を作成
    docs = []
    for _, row in d.iterrows():
        doc = f"{row['Prefecture']} 人口{row['A1101']//10000}万 出生{row['A4101']//100}百"
        docs.append(doc)
    v = TfidfVectorizer()
    M = v.fit_transform(docs)
    print(M.shape)
    
    📤 実行例(実測) (47, 134)

    💬 47 県それぞれに「県名・人口◯万・出生◯百」の 3 語の文書を作ったので、語の延べ数は 141 だが、語彙は 134 語になった。重複したのは「人口129万」「出生69百」など 6 語だけで、ほとんどの語が 1 つの県にしか出てこない。数値をそのまま語にすると値が 1 つ違うだけで別の語になるため、TF-IDF の「共通する語で文書を比べる」仕組みがほとんど働かない行列になっていることを、この 134 という数が示している。

  2. 問題 2:「東京都」の TF-IDF ベクトルから値が最大の単語を取り出せ。
    ▼ 解答
    1
    2
    3
    4
    5
    import numpy as np
    idx = list(d['Prefecture']).index('東京都')
    row = M[idx].toarray().flatten()
    top = row.argsort()[-3:][::-1]
    print([v.get_feature_names_out()[i] for i in top])
    
    📤 実行例(実測) ['東京都', '出生863百', '人口1408万']

    💬 東京都の上位 3 語は「東京都」「出生863百」「人口1408万」で、東京都の文書の 3 語すべてが並んだ。3 語ともほかの県に出てこないので IDF はどれも ln(48/2)+1≈4.18 と同じになり、正規化後の重みはそろって 1/√3≈0.577 になる。表示順は同点のなかでの argsort の並びにすぎず、「県名が最も特徴的」という意味は無い。

  3. 問題 3:コサイン類似度で「東京都」に最も似た都道府県 TOP 3 を求めよ。
    ▼ 解答
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from sklearn.metrics.pairwise import cosine_similarity
    idx = list(d['Prefecture']).index('東京都')
    sims = cosine_similarity(M[idx], M).flatten()
    sims[idx] = -1                                  # 自分自身を除く
    order = sims.argsort()[::-1]
    # 類似度 0 の県を「似ている順」に並べても意味が無いので、語を共有する県だけを出す
    shared = [i for i in order if sims[i] > 0]
    print('東京都と語を 1 つでも共有する県:', len(shared), '県')
    for i in shared[:4]:
        print(d.iloc[i]['Prefecture'], round(sims[i], 3))
    
    📤 実行例(実測) 東京都と語を 1 つでも共有する県: 0 県

    💬 東京都の 3 語は他のどの県の文書にも出てこないため、コサイン類似度は 46 県すべてで 0 になり、語を共有する県は 0 県だった。類似度で並べ替えて上位 4 県を表示すると、類似度 0.0 の沖縄県・千葉県などが「似ている県」のように出てしまうが、それは同点の中の並び順にすぎないので、ここでは類似度が 0 より大きい県だけを数えている。数値を語として扱うなら「人口100万台」のように幅でまとめ、語を共有させてから類似度を測る必要がある。

    実測では東京都との類似度は全て 0.000 になります。 既定 tokenizer が「人口1408万」等を丸ごと 1 トークン化し、 東京都は他県と共有語を持たないためです(各県が独自語彙)。 意味ある TOP 3 を得るには数値を分割・ビン化してトークンを共有させる前処理が必要。

  4. 問題 4:TF-IDF と BM25 の違いを 3 つ述べよ。
    ▼ 解答
    (1) BM25 は単語頻度を飽和させる (高頻度を抑制)。 (2) BM25 は文書長正規化 (b パラメータ) を明示。 (3) BM25 は確率的検索モデル由来、 TF-IDF はヒューリスティック。 検索エンジンの実用標準は BM25。
  5. 問題 5:TfidfVectorizer のパラメータ min_df, max_df, ngram_range の意味を述べよ。
    ▼ 解答
    min_df: この回数未満の単語を除去 (typo除外)。 max_df: この割合超で出現する単語を除去 (汎用語除外)。 ngram_range=(1,2): unigram + bigram も使う。

📖 関連用語辞典(10 語)

TF
Term Frequency。 文書内の単語出現頻度。
IDF
Inverse Document Frequency。 単語の希少性。 $\log(N/df)$。
TF-IDF
TF × IDF。 「文書 d でその単語がどれくらい特徴的か」。
BM25
TF-IDF の改良版。 単語頻度を飽和し文書長正規化。 検索エンジン標準。
ストップワード
「の」「は」など意味の薄い高頻度語。 通常除去。
コサイン類似度
ベクトル間の角度の cos。 TF-IDF 類似度の標準。
文書
テキストの 1 単位。 ニュース記事・FAQ 回答・都道府県プロフィール等。
コーパス
文書集合 (corpus)。
ベクトル空間モデル
文書を語彙次元のベクトルとして表現するモデル。 TF-IDF はその実装。
LSA / LSI
TF-IDF + SVD で潜在意味次元を抽出。 TF-IDF の拡張。

🧮 実値で計算してみる

3 文書のミニコーパス:

「猫」の TF-IDF を計算:

「は」は全 3 文書に出るので IDF = log(3/3) = 0、 TF-IDF も 0。 「魚」は d1 にしか出ないので IDF = log(3/1) ≈ 1.10 と大きく、 d1 で重要な単語と判定されます。

🧮 数式に値を入れて手で計算する: TF-IDF

合成 3 文書で単語 "data" の TF-IDF を計算する。

Step 1: 統計

D1 (100 語): data 5 → TF=0.05 D2 (100 語): data 2 → TF=0.02 D3 (100 語): data 0 → TF=0 df = 2 (D1, D2) N = 3

Step 2: IDF と TF-IDF

IDF = log(3/2) ≈ 0.405 TF-IDF(D1) = 0.05 × 0.405 = 0.0203 TF-IDF(D2) = 0.02 × 0.405 = 0.0081

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
tf = np.array([0.05, 0.02, 0])
N = 3; df = 2
idf = np.log(N/df)
tfidf = tf * idf
print(f"IDF: {idf:.3f}")
print(f"TF-IDF: {tfidf.round(4)}")

📤 実行結果

IDF: 0.405 TF-IDF: [0.0203 0.0081 0. ]

💬 手計算 (Step 2) と Python 出力が完全一致。

🐍 Python での扱い

最小再現コード。 SSDSE-B のような実データを前提に、 4〜8 行で動く例です:

1
2
3
4
5
6
from sklearn.feature_extraction.text import TfidfVectorizer
docs = ['猫 は 魚 が 好き', '犬 は 肉 が 好き', '猫 と 犬 は 動物']
vec = TfidfVectorizer(token_pattern=r'\S+')
X = vec.fit_transform(docs)
print(vec.get_feature_names_out())
print(X.toarray().round(3))
📤 実行例(実測) ['が' 'と' 'は' '動物' '好き' '犬' '猫' '肉' '魚'] [[0.433 0. 0.336 0. 0.433 0. 0.433 0. 0.569] [0.433 0. 0.336 0. 0.433 0.433 0. 0.569 0. ] [0. 0.534 0.315 0.534 0. 0.406 0.406 0. 0. ]]

💬 3 文すべてに出てくる「は」は IDF が ln(4/4)+1=1 と最小で、1 文目の重みは 0.336 にとどまる。1 文目にしか出ない「魚」は IDF が ln(4/2)+1≈1.69 と最大で、重みも 0.569 と文中で最も高い。2 文に出る「が」「好き」「猫」は 0.433 で、その間に入る。各行は長さ 1 に正規化されているので、1 文にしか出ない語(「と」「動物」、各 0.534)を 2 つ含む 3 文目では、「犬」「猫」の重みが 0.406 と 1・2 文目の 0.433 より小さくなる点も読み取れる。

補足:ライブラリのバージョンや前処理状態によって出力は変わります。 自分の環境で動かすときは pip list でバージョンを確認し、 入力 CSV のパス・列名を実態に合わせてください。

🐍 Python 完全コード(4 要素ナレーション付き)

コード 1:SSDSE-B-2026 で「都道府県プロフィール文書」を TF-IDF 化

🎯 このコードでやること:47 都道府県の主要指標を文字列化して文書集合とし、 TF-IDF ベクトル化する。

📥 入力データ:SSDSE-B-2026 から 2023 年データ。 都道府県名 + 主要指標カテゴリで文書を作成。

doc[0] = '北海道 人口509万 出生244百 死亡751百 高齢化33' doc[1] = '青森県 人口118万 出生56百 死亡208百 高齢化35' ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
d  = df[df['年度']==2023].copy()
d['高齢化率'] = (d['65歳以上人口']/d['総人口']*100).astype(int)

# 都道府県プロフィール文書を作成
def make_doc(row):
    return (f"{row['都道府県']} "
            f"人口{row['総人口']//10000}万 "
            f"出生{row['出生数']//100}百 "
            f"死亡{row['死亡数']//100}百 "
            f"高齢化{row['高齢化率']}")

docs = [make_doc(r) for _, r in d.iterrows()]

v = TfidfVectorizer()
M = v.fit_transform(docs)

print(f"文書数 : {M.shape[0]}")
print(f"語彙数 : {M.shape[1]}")
print(f"非ゼロ : {M.nnz}")
print(f"\nサンプル文書:")
print(f"  {docs[12]}")  # 東京都

📤 実行結果:

文書数 : 47 語彙数 : 191 非ゼロ : 235 サンプル文書: 東京都 人口1408万 出生863百 死亡1372百 高齢化22

💬 結果の読み方:47 都道府県 × 191 語彙のスパース TF-IDF 行列(非ゼロ 235=各文書ちょうど 5 トークン × 47 文書)。 各都道府県の特徴的な単語(東京なら「人口1408万」「出生863百」など)が高 TF-IDF 値を持つ。 47×191 でも疎で、 巨大コーパスでも省メモリ。 なお既定の TfidfVectorizer は空白・記号区切りのため「人口1408万」がそのまま 1 トークン化し、 数値ごと語彙が分かれる点に注意(後述の類似度に影響)。

コード 2:「東京都」に最も似た都道府県を TF-IDF + コサイン類似度で発見

🎯 このコードでやること:東京都の TF-IDF ベクトルと他 46 都道府県のベクトルとのコサイン類似度を計算し TOP 3 を表示。

📥 入力データ:前のコードで M (47×191) を取得済み。

M: shape=(47, 191) スパース行列
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.metrics.pairwise import cosine_similarity

idx = list(d['都道府県']).index('東京都')
sims = cosine_similarity(M[idx], M).flatten()

# 自分自身を除外
sims[idx] = -1

top = sims.argsort()[-5:][::-1]
print("東京都に類似する都道府県 TOP 5:")
for i in top:
    print(f"  {d.iloc[i]['都道府県']:<6s} : {sims[i]:.4f}")

📤 実行結果:

東京都に類似する都道府県 TOP 5: 沖縄県 : 0.0000 千葉県 : 0.0000 静岡県 : 0.0000 岐阜県 : 0.0000 長野県 : 0.0000

💬 結果の読み方(重要な落とし穴):実際に走らせると東京都との類似度は全て 0.0000。 これは失敗ではなく、 既定 TfidfVectorizer の tokenizer の性質そのものを可視化した結果です。 「人口1408万」「出生863百」のように数値がまるごと 1 トークンになるため、 東京都の 5 語(県名+4 指標値)は他県と 1 語も共有せず、 コサイン類似度が 0 になります。 実際、 東京都は高齢化率 22(全国最小・固有値)を含むため共有語がゼロでした。 一方で高齢化率などの階級値が一致する地方県同士だけは僅かに類似(47 県 1081 ペア中 95 ペアのみ非ゼロ、 最大は奈良県×愛媛県の 0.3808)。 教訓:数値をそのままトークン化すると各県が「独自語彙の孤島」になり、 意味ある類似度が測れない。 実運用では数値をビン化・単位語に分割するか、 tokenizer を差し替える必要があります(→ FAQ「日本語 TF-IDF」)。

コード 3:TF-IDF の上位語を都道府県別に抽出

🎯 このコードでやること:各都道府県の TF-IDF 値が最大の単語 (= 最も特徴的な単語) を抽出する。

📥 入力データ:前述 v, M, d を使用。

v.get_feature_names_out() = ['三重県', '京都府', '人口100万', ..., '鹿児島県'] # 全191語
1
2
3
4
5
6
7
8
9
import numpy as np
features = v.get_feature_names_out()

print("各都道府県の最特徴語 (TF-IDF 最大):")
for i in range(0, 47, 5):  # 5 件刻みで表示
    row = M[i].toarray().flatten()
    top_idx = row.argsort()[-3:][::-1]
    top_words = [features[j] for j in top_idx]
    print(f"  {d.iloc[i]['都道府県']:<6s}: {', '.join(top_words)}")

📤 実行結果:

各都道府県の最特徴語 (TF-IDF 最大): 北海道 : 死亡751百, 北海道, 人口509万 山形県 : 人口102万, 山形県, 出生51百 埼玉県 : 埼玉県, 人口733万, 出生421百 富山県 : 人口100万, 富山県, 出生55百 岐阜県 : 出生104百, 死亡260百, 人口193万 京都府 : 人口253万, 京都府, 死亡307百 鳥取県 : 鳥取県, 人口53万, 死亡82百 徳島県 : 人口69万, 出生39百, 徳島県 佐賀県 : 佐賀県, 死亡111百, 出生51百 鹿児島県: 鹿児島県, 出生98百, 人口154万

💬 結果の読み方:各都道府県の数値情報がそのまま「最特徴語」として浮上。 IDF 大 (他に出ない数値)、 TF 大 (自身の文書に出る) で TF-IDF 最大値。 TF-IDF は「数値の指紋」も検出できる。 実際のテキスト分析でも、 ユニークな専門用語が高 TF-IDF で表面化する。

コード 4:TF-IDF + KMeans でクラスタリング

🎯 このコードでやること:TF-IDF ベクトルで 47 都道府県を 4 クラスタに分け、 「特徴の似た都道府県群」を発見。

📥 入力データ:M (47, 191) を KMeans に入力。

M: TF-IDF 行列
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.cluster import KMeans
from collections import defaultdict

km = KMeans(n_clusters=4, random_state=0, n_init=10).fit(M)

groups = defaultdict(list)
for i, c in enumerate(km.labels_):
    groups[c].append(d.iloc[i]['都道府県'])

for c, prefs in sorted(groups.items()):
    print(f"\nクラスタ {c} ({len(prefs)}県):")
    print("  " + ", ".join(prefs[:10]) + ("..." if len(prefs)>10 else ""))

📤 実行結果:

クラスタ 0 (7県): 茨城県, 栃木県, 群馬県, 石川県, 静岡県, 三重県, 広島県 クラスタ 1 (28県): 青森県, 岩手県, 宮城県, 秋田県, 山形県, 埼玉県, 千葉県, 東京都, 神奈川県, 長野県... クラスタ 2 (5県): 福井県, 山梨県, 岐阜県, 岡山県, 佐賀県 クラスタ 3 (7県): 北海道, 福島県, 新潟県, 富山県, 鳥取県, 宮崎県, 鹿児島県

💬 結果の読み方(退化に注意):seed 固定(random_state=0, n_init=10)で実行すると 7/28/5/7 県という大きく偏ったクラスタになります。 前のコードで見たとおり数値がトークン化され県間の共有語がほぼ無いため、 ベクトルはほぼ直交し、 クラスタ分けは主に高齢化率の階級値が一致する少数ペアに引きずられます(28 県の巨大クラスタは「他とほぼ無関係=寄せ集め」に近い)。 「大都市圏/小規模県」のようなきれいな分離にはなりません。 教訓:特徴設計(数値のビン化・正規化)を怠ると、 KMeans は意味あるクラスタを作れない。 数値テキストをそのまま使う限界を体験できる好例です。

❓ FAQ 20 問

Q1. TF-IDF と BM25 どちらを使う?
本格的な検索なら BM25。 教育・軽量実装なら TF-IDF。 Elasticsearch のデフォルトは BM25。
Q2. 日本語に TF-IDF をどう適用?
MeCab/Sudachi で単語分割 → TfidfVectorizer の tokenizer 引数に渡す。 ストップワード辞書も必要。
Q3. TF-IDF の値域
理論的には 0 以上の任意値。 sklearn は L2 正規化されるため各ベクトル長が 1。 値そのものは [0, 1] 程度に収まる。
Q4. IDF が負になることはある?
全文書に出る単語 ($\text{df}=N$) で $\log(N/N)=0$。 sklearn の smooth_idf=True なら常に正値。
Q5. TF-IDF と Word2Vec の関係
TF-IDF は疎・解釈容易、 Word2Vec は密・類似語捕捉。 用途で使い分け。 ハイブリッドも一般的。
Q6. 文書類似度は cos 以外に?
ユークリッド距離、 Jaccard 係数 (二値化版)、 BM25 スコア。 TF-IDF + cos が標準。
Q7. TF-IDF の計算量
学習 $O(N \cdot \bar{L})$ ($\bar{L}$ 平均文書長)、 ベクトル化 $O(N \cdot \bar{L})$。 大規模でも線形。
Q8. TF-IDF のメモリ消費
スパース行列なので $O(\text{非ゼロ要素数})$。 100 万文書 × 10 万語彙でも 1GB 程度。
Q9. TF-IDF をオンライン更新したい
HashingVectorizer + TfidfTransformer の組み合わせ。 ただし IDF を再計算する必要あり。
Q10. TF-IDF と Naive Bayes の組合せ
古典的なテキスト分類の鉄板組み合わせ。 TF-IDF + MultinomialNB で十分高精度。
Q11. min_df, max_df の推奨値
min_df=2 か 0.001、 max_df=0.95 が一般的。 ノイズ語と汎用語を同時に除去。
Q12. ngram_range=(1,2) と (1,3) の違い
bigram まで vs trigram まで。 trigram は次元爆発するため通常 bigram で十分。
Q13. TF-IDF が役立たない場合は?
極端に短い文書 (Twitter 1 文書 1 単語)、 マルチモーダル (画像+テキスト)、 文脈依存意味 (BERT の領域)。
Q14. BM25 を Python で使う
rank_bm25 パッケージ。 Elasticsearch でクエリすると標準で BM25 採用。
Q15. TF-IDF + Logistic Regression のテキスト分類精度
20 Newsgroups で 85-90%。 BERT (90-95%) より低いが学習は秒単位で完了。
Q16. 文書埋め込みの代替手段は?
Sentence-BERT、 Universal Sentence Encoder、 OpenAI text-embedding-ada-002 など。 高精度だが計算コスト大。
Q17. 47 都道府県を TF-IDF で扱う意義
数値テキストでも TF-IDF が機能することを体験できる。 教育用に小規模で扱いやすい。
Q18. TF-IDF の解釈性
高い解釈性が強み。 「この文書ではこの単語が特徴的」と人間が読める。 ブラックボックスな埋め込みより説明可能。
Q19. TF-IDF を勉強する最良の本
Manning『Introduction to Information Retrieval』、 Aggarwal『Machine Learning for Text』。 Stanford CS276 講義もおすすめ。
Q20. SSDSE-B-2026 で TF-IDF を学ぶ最小例
上記コードブロック (都道府県プロフィール) が代表例。 47×191 行列で TF-IDF・コサイン類似度・クラスタリングまで体験可。

📖 TF-IDF の包括ガイド(追補編)

🔍 TF-IDF の多角的解釈

TF-IDF の情報理論的解釈

IDF $= -\log P(t)$ = 単語の自己情報量。 TF と掛け合わせ「情報量加重頻度」。

BM25 の確率モデル由来

BM25 は Probability Ranking Principle から導出。 「関連文書である確率」を推定。

文書ベクトル空間の幾何

コサイン類似度 = 単位球面上の角度。 高次元疎では距離より角度が安定。

Spärck Jones の貢献

1972 年に IDF を提案、 「珍しさが識別力に直結」を示した。 IR の基礎。

Salton のベクトル空間モデル

1975 年に文書をベクトル化する枠組を確立。 現代 IR の礎。

📓 「TF-IDF」をさらに深掘り — 実データ実践ノート

SSDSE-B-2026 を使った段階的ハンズオン。 初学者→中級→上級と順に深めるシナリオ構成です。

ステップ 1: コーパス準備

SSDSE-B-2026 から 2023 年 47 都道府県の指標を文字列化。

ステップ 2: Vectorizer 作成

TfidfVectorizer(min_df=1)。 47 文書なので min_df 緩めに。

ステップ 3: 学習・変換

M = v.fit_transform(docs)。 形状 (47, ~200)。

ステップ 4: 語彙確認

v.get_feature_names_out()[:20] で最初の 20 語。

ステップ 5: IDF 確認

v.idf_。 大 IDF = レア語 = 識別力大。

ステップ 6: 類似度計算

cosine_similarity(M[0], M) で 1 都道府県 vs 全 46 県。

ステップ 7: TOP K 検索

argsort() で上位 5 件を抽出。

ステップ 8: 分類モデル

ラベル列 (例:人口減少率) を作り LogisticRegression で予測。

ステップ 9: クラスタリング

KMeans(n_clusters=4).fit(M) で類似都道府県群を発見。

ステップ 10: BM25 と比較

BM25Okapi でランキングを TF-IDF と比較。

📋 TF-IDF チートシート

実務で頻用するコード・概念・公式を 1 ページにまとめた早見表。 印刷して机に貼っておくと便利です。

領域項目説明
作成TfidfVectorizer()デフォルト設定
学習v.fit_transform(docs)コーパスから TF-IDF 行列
変換v.transform(['新文書'])学習済みベクタイザで変換
語彙v.get_feature_names_out()語彙リスト
IDFv.idf_各単語の IDF
min_dfmin_df=22 文書未満の語除去
max_dfmax_df=0.9595% 超出現語除去
ngramngram_range=(1,2)uni + bigram
stop_wordsstop_words='english'英語ストップワード
max_featuresmax_features=10000上位 10k 語のみ
L2 正規化norm='l2'コサイン類似度準備
対数 TFsublinear_tf=True1+log(tf)
IDF 平滑化smooth_idf=True+1 平滑化
トークナイザtokenizer=lambda s: ...カスタム分割
コサイン類似cosine_similarity(M[0], M[1])ベクトル間類似度
BM25from rank_bm25 import BM25Okapi改良版
HashingHashingVectorizer()オンライン用 (語彙無し)
CountCountVectorizer()TF のみ (IDF なし)
PipelinePipeline([('tfidf', TfidfVectorizer()), ('clf', LogReg())])前処理 + 分類
MeCabMeCab.Tagger('-Owakati')日本語分かち書き

🐍 SSDSE-B-2026 × TF-IDF 追加コード集 (4 要素ナレーション)

本編のコードに加え、 さらに 4 種の発展的コード例を 4 要素 (🎯/📥/📤/💬) 付きで提示。 段階的に「読む→動かす→改造する」を体験できます。

追加コード 1:BM25 と TF-IDF のランキング比較

🎯 このコードでやること:同じクエリで BM25 と TF-IDF のランキングを比較。

📥 入力データ:SSDSE-B-2026 の関連カラム。

前述コードブロックの d, M, G 等を利用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from rank_bm25 import BM25Okapi
docs = [r['都道府県']+' '+str(r['総人口']//10000)+'万' for _, r in d.iterrows()]
tokenized = [doc.split() for doc in docs]
bm25 = BM25Okapi(tokenized)

query = '1000 万人'.split()
bm25_scores = bm25.get_scores(query)

tfidf_q = v.transform([' '.join(query)])
tfidf_scores = (M @ tfidf_q.T).toarray().flatten()

import pandas as pd
result = pd.DataFrame({
    '都道府県': d['都道府県'].values,
    'BM25': bm25_scores,
    'TF-IDF': tfidf_scores
}).sort_values('BM25', ascending=False).head(5)
print(result)

📤 実行結果:

都道府県 BM25 TF-IDF 北海道 0.0 0.0 徳島県 0.0 0.0 大阪府 0.0 0.0 兵庫県 0.0 0.0 奈良県 0.0 0.0

💬 結果の読み方(クエリ側も同じ落とし穴):クエリ '1000 万人'.split() は ['1000', '万人'] になりますが、 文書側のトークンは「924万」「509万」のように数値と単位が一体で、 「1000」も「万人」も語彙に存在しません。 結果、 BM25・TF-IDF とも全県スコア 0(並び順は元の県順のまま)。 これは前述の tokenizer 退化がクエリ照合でも同様に起きることを示します。 意味あるランキングを得るには、 文書とクエリでトークン化の粒度を揃える(例:数値を「1000」「万」に分割)必要があります。 BM25 の理論的利点(頻度飽和・文書長正規化)を体感するには、 まず語彙が噛み合う前処理が前提です。

追加コード 2:TF-IDF + LinearSVC でテキスト分類

🎯 このコードでやること:都道府県プロフィール文書を「人口 100 万以上か否か」で 2 値分類。

📥 入力データ:SSDSE-B-2026 の関連カラム。

前述コードブロックの d, M, G 等を利用。
1
2
3
4
5
6
7
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report

labels = (d['総人口'] > 1_000_000).astype(int).values
clf = LinearSVC().fit(M, labels)
pred = clf.predict(M)
print(classification_report(labels, pred))

📤 実行結果:

precision recall f1-score support 0 1.00 1.00 1.00 10 1 1.00 1.00 1.00 37 accuracy 1.00 47 macro avg 1.00 1.00 1.00 47

💬 結果の読み方(=リーク気味の満点):ラベルは「総人口 > 100 万」(該当 37 県/非該当 10 県)で定義。 実行すると学習データ上で Accuracy 1.00(support は 10/37)になります。 これは分類器が優秀というより、 各文書に「人口○○万」という人口そのものを表すトークンが入っているため、 高次元疎な線形 SVM がほぼ暗記してしまうから。 訓練データで評価している点も含め過学習・情報リークの典型で、 汎化性能の指標にはなりません。 正しく評価するなら人口関連トークンを除く、 または train/test 分割が必須です。

追加コード 3:Truncated SVD で次元削減 (LSA)

🎯 このコードでやること:TF-IDF 行列を SVD で 5 次元に圧縮し、 潜在意味次元を抽出。

📥 入力データ:SSDSE-B-2026 の関連カラム。

前述コードブロックの d, M, G 等を利用。
1
2
3
4
5
6
7
8
9
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD(n_components=5, random_state=0).fit(M)
print(f"説明分散比: {svd.explained_variance_ratio_}")
print(f"累積:       {svd.explained_variance_ratio_.cumsum()}")

# 5 次元埋め込み
emb = svd.transform(M)
print(f"\n5 次元埋め込み shape: {emb.shape}")
print(f"東京都の埋め込み: {emb[12]}")

📤 実行結果:

説明分散比: [0.0236 0.0312 0.0356 0.0311 0.0323] 累積: [0.0236 0.0547 0.0903 0.1214 0.1537] 5 次元埋め込み shape: (47, 5) 東京都の埋め込み: [0.0344, 0.0031, -0.012, 0.0129, -0.0332]

💬 結果の読み方(低い分散=ここでも退化):191 次元 → 5 次元に圧縮しても累積寄与率はわずか 15.4%(各成分 2〜4%)。 これも tokenizer 退化の帰結で、 ベクトルがほぼ直交しているため少数の潜在次元では説明しきれません(意味ある LSA では上位数次元で数十%に達するのが普通)。 東京都の埋め込みも全成分が 0 近傍で、 5 次元では都道府県を十分表現できていません。 LSA の威力を出すには、 まず語彙が共有される前処理が必要という点まで含めて教材化できます。

追加コード 4:都道府県別 TOP 5 特徴語の一覧

🎯 このコードでやること:47 都道府県全てについて TF-IDF 上位 5 語を表示。

📥 入力データ:SSDSE-B-2026 の関連カラム。

前述コードブロックの d, M, G 等を利用。
1
2
3
4
5
6
7
8
import numpy as np
features = v.get_feature_names_out()
print("都道府県別 TOP 5 特徴語:\n" + "="*60)
for i in range(47):
    row = M[i].toarray().flatten()
    top_idx = row.argsort()[-5:][::-1]
    words = [features[j] for j in top_idx]
    print(f"{d.iloc[i]['都道府県']:<6s}: {', '.join(words)}")

📤 実行結果:

都道府県別 TOP 5 特徴語: ============================================================ 北海道 : 死亡751百, 北海道, 人口509万, 出生244百, 高齢化33 青森県 : 死亡208百, 人口118万, 青森県, 出生56百, 高齢化35 岩手県 : 岩手県, 出生54百, 人口116万, 死亡196百, 高齢化34 ... 沖縄県 : 高齢化23, 出生125百, 沖縄県, 死亡151百, 人口146万

💬 結果の読み方:47 県すべてに固有の特徴語が抽出される。 数値の桁が各県のサイズを反映、 県名自体も 1 文書にしか出ないため高 TF-IDF。

🧪 実データで確かめる:SSDSE-B-2026 の「指標名」109 個を本物の文書として TF-IDF 検索する

ここまでのコードは数値を文字列にした「プロフィール文書」でしたが、SSDSE-B-2026 には本物の日本語テキストも入っています。2 行目の列名(「65歳以上人口」「一般病院数」など、年度・地域コード・都道府県を除く 109 個)です。1 つの指標名を 1 文書とみなし、形態素解析(janome)で名詞に分けてから TF-IDF にかけ、IDF の大小と、検索語に近い指標名を調べます。

🎯 このコードでやること:SSDSE-B-2026 の指標名 109 個を janome で名詞に分けて TF-IDF 行列を作り、文書頻度 df と IDF の上位語を表にし、「65歳以上人口(女)」に近い指標名をコサイン類似度で 5 件探す。

📥 入力データ:SSDSE-B-2026.csv の 2 行目(日本語の列名)のうち 4 列目以降の 109 個。例:「65歳以上人口」→「65 歳 以上 人口」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import pandas as pd
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer

names = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', nrows=2, header=None).iloc[1, 3:].tolist()
tok = Tokenizer()
def words(s):                                     # 名詞(数字を含む)だけを残し、助詞・記号は捨てる
    return [t.surface for t in tok.tokenize(s) if t.part_of_speech.startswith('名詞')]
docs = [' '.join(words(s)) for s in names]
print(len(docs), '個の指標名。例:', names[12], '→', docs[12])

vec = TfidfVectorizer(tokenizer=str.split, token_pattern=None, lowercase=False)
X = vec.fit_transform(docs)
idf = pd.Series(vec.idf_, index=vec.get_feature_names_out())
df_count = pd.Series(np.asarray((X > 0).sum(0)).ravel(), index=idf.index)
tab = pd.DataFrame({'df': df_count, 'IDF': idf.round(3)}).sort_values('df', ascending=False)
print(tab.head(6).to_string())
print('IDF 最大(df=1)の語の例:', ', '.join(tab[tab.df == 1].index[:6]))

q = vec.transform([' '.join(words('65歳以上人口(女)'))])
sim = (X @ q.T).toarray().ravel()
top = np.argsort(-sim)[:5]
print('クエリ「65歳以上人口(女)」に近い指標名:')
for i in top:
    print(f'  {sim[i]:.3f}  {names[i]}')

📤 実行結果:

109 個の指標名。例: 65歳以上人口 → 65 歳 以上 人口 df IDF 数 61 1.573 者 18 2.756 人口 15 2.928 以上 14 2.992 人 13 3.061 世帯 11 3.216 IDF 最大(df=1)の語の例: 食料, 高値, 申込, 離婚, 用品, 病院 クエリ「65歳以上人口(女)」に近い指標名: 1.000 65歳以上人口(女) 0.897 65歳以上人口 0.805 65歳以上人口(男) 0.583 総人口(女) 0.513 15歳未満人口(女)

💬 結果の読み方:最も多くの指標名に出る語は「数」(出生数・死亡数・病院数など 61 個)で、IDF は ln((1+109)/(1+61)) + 1 = 1.573 と最小です。逆に「食料」「病院」のように 1 つの指標名にしか出ない語は IDF が最大になります。検索では「65歳以上人口(女)」自身が 1.000、「65歳以上人口」が 0.897、男性版が 0.805 と、共有する語が多い順に並び、「総人口(女)」(0.583)は「人口」と「女」だけを共有するので下がります。TF-IDF が「どの語を共有しているか」と「その語がどれだけ珍しいか」だけで近さを決めていることが、実際の列名で確かめられます。

🧪 実データで確かめる:IDF が無いと「数」で終わる 60 個の指標名が同点で並ぶ

IDF の効き目を確かめるため、「病院数」で検索したときの結果を、出現回数だけのベクトル(TF)と TF-IDF で比べます。どちらも長さ 1 に正規化したうえで余弦類似度を計算します。

🎯 このコードでやること:指標名 109 個について、出現回数だけのベクトルと TF-IDF ベクトルで「病院数」を検索し、類似度が 0 より大きい指標名の数と上位 4 件を比べる。

📥 入力データ:上と同じ 109 個の指標名(janome で名詞に分割)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import pandas as pd
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.preprocessing import normalize

names = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', nrows=2, header=None).iloc[1, 3:].tolist()
tok = Tokenizer()
def words(s):
    return [t.surface for t in tok.tokenize(s) if t.part_of_speech.startswith('名詞')]
docs = [' '.join(words(s)) for s in names]
opt = dict(tokenizer=str.split, token_pattern=None, lowercase=False)
query = ' '.join(words('病院数'))
for name, v in [('出現回数のみ (TF)', CountVectorizer(**opt)), ('TF-IDF', TfidfVectorizer(**opt))]:
    X = normalize(v.fit_transform(docs).astype(float))      # どちらも長さ 1 に揃えて余弦類似度
    q = normalize(v.transform([query]).astype(float))
    sim = (X @ q.T).toarray().ravel()
    order = np.argsort(-sim, kind='stable')
    print(f'{name}: 類似度 > 0 の指標 {int((sim > 0).sum())} 個 / 上位 4 位')
    for i in order[:4]:
        print(f'   {sim[i]:.3f}  {names[i]}')

📤 実行結果:

出現回数のみ (TF): 類似度 > 0 の指標 61 個 / 上位 4 位 0.816 一般病院数 0.500 出生数 0.500 死亡数 0.500 幼稚園数 TF-IDF: 類似度 > 0 の指標 61 個 / 上位 4 位 0.832 一般病院数 0.112 中学校数 0.112 短期大学数 0.112 大学数

💬 結果の読み方:どちらも 1 位は「病院」を含む唯一の指標名「一般病院数」ですが、2 位以下が違います。出現回数だけでは「数」を共有するだけの出生数・死亡数・幼稚園数などが類似度 0.500 で同点に並び、検索語と関係の薄い指標名が上位を埋めます。TF-IDF では「数」の重みが IDF 1.573 に抑えられるので、2 位以下は 0.112 以下に下がり、1 位との差がはっきりします。類似度が 0 より大きい指標名は 61 個で変わらないので、IDF は「どれがヒットするか」ではなく「ヒットした文書の並び方」を変えていることが分かります。

⚠️ よくある落とし穴

TF-IDF を実務で扱うとき、 多くの分析者が同じところでつまずきます。 代表的な失敗パターンを先回りで押さえておくと、 後工程のトラブルを大幅に減らせます。

❌ ストップワード処理
「は」「が」「the」のような機能語は IDF で弱まりますが、 計算前に除いた方がノイズが減ります。
❌ 対数の底
scikit-learn は log_e、 教科書は log_2 や log_10 が多い。 ライブラリ間で値が違うのは底の違い。
❌ 正規化
scikit-learn はデフォルトで L2 正規化。 「TF を文書長で割った」だけの素朴版とは値が違う。
❌ 文脈を捉えられない
TF-IDF は 順序を無視 (Bag-of-Words)。 「猫が犬を追う」と「犬が猫を追う」が同じベクトルに。
❌ 低頻度語の過剰重要視
1 回しか出ない誤字も IDF が高くなる。 min_df で足切りを。

※ 上記は文献調査・現場経験で報告される頻度の高い注意点。 ドメインや手法のバージョンによって追加の落とし穴がある場合があります。

⚠️ さらに踏み込んだ失敗パターン 10 連

初学者を超えた中級者がハマる「2 周目の失敗例」を集めました。 本編の 5 件と合わせて 15 件のチェックリストとして活用してください。

⚠️ 日本語のトークナイズ忘れ
デフォルトは英語の空白区切り。 MeCab/Sudachi が必須。
⚠️ min_df が緩すぎ
min_df=1 だと typo まで含まれノイズ。 通常 min_df=2 以上。
⚠️ max_df 設定なし
「は」「の」が IDF 低で残る。 max_df=0.95 で除去。
⚠️ 大文字小文字
デフォルトは lowercase。 固有名詞 (Apple) が apple に。
⚠️ ハイフン語
token_pattern で英数字+ハイフンを許可しないとちぎれる。
⚠️ 巨大語彙
10 万語超で OOM。 max_features=10k〜50k で打ち切り。
⚠️ L1 正規化
デフォルトの L2 が cos 類似度に最適。 L1 は文書長に弱い。
⚠️ テストデータも fit
fit_transform をテストにも適用するとリーク。 train で fit、 test は transform のみ。
⚠️ BM25 と TF-IDF 混同
BM25 は飽和あり、 文書長正規化あり。 計算式が異なる。
⚠️ 文書埋め込みと混同
TF-IDF は疎、 BERT は密。 用途で使い分け。

📖 TF-IDF 関連語拡張辞典(20 語)

本編の 10 語に加え、 さらに専門用語 20 個を整理。 文献を読むときの「分からない単語チェッカー」として使えます。

ベクトル空間モデル
文書を語彙次元のベクトル化。 TF-IDF が実装。
情報検索 (IR)
クエリと文書のマッチング学問。
BM25
TF-IDF 改良版。 検索エンジン標準。
LSA / LSI
TF-IDF + SVD で潜在次元抽出。
LDA
確率的トピックモデル。 文書を分布で表現。
Word2Vec
単語分散表現。 類似性捕捉。
GloVe
共起行列ベース埋め込み。
FastText
サブワード対応埋め込み (Facebook)。
BERT
Transformer 文脈埋め込み。 SOTA。
Sentence-BERT
文埋め込み専用 BERT。 高速類似度。
Cosine Similarity
ベクトル角度の cos。 IR で標準。
Jaccard Similarity
集合の |A∩B|/|A∪B|。 二値文書。
MinHash / LSH
近似類似度検索。 大規模対応。
Inverted Index
単語 → 文書 ID リストの索引。
Stemming
語幹抽出 (英語)。 Porter Stemmer。
Lemmatization
原形化。 語形変化を統一。
Stop Words
意味の薄い高頻度語。 通常除去。
N-gram
連続 N 単語。 bigram, trigram が典型。
Document Frequency
単語が出る文書数。 IDF の元。
Term Frequency
文書内単語頻度。 TF。

❓ FAQ 追補 20 問(中〜上級者向け)

追補 Q1. TF-IDF と BERT のハイブリッド
TF-IDF で粗い検索 → BERT で再ランキング (rerank)。 大規模 IR の標準。
追補 Q2. BM25 のパラメータ
k1=1.2 (頻度飽和)、 b=0.75 (文書長正規化)。 経験値。
追補 Q3. TF-IDF を多言語に
言語ごとにトークナイザを切替。 Sudachi (日)、 spaCy (英・多言語)。
追補 Q4. 文書クラスタリングと TF-IDF
KMeans より階層クラスタや HDBSCAN が文書には向く。
追補 Q5. Naive Bayes + TF-IDF
MultinomialNB と組合せ。 古典的だが今でも spam 分類で現役。
追補 Q6. Linear SVM + TF-IDF
テキスト分類で最強の組合せのひとつ。 高次元疎で SVM が機能。
追補 Q7. TF-IDF の保存
joblib.dump(v, 'v.pkl') で永続化。 推論時に joblib.load。
追補 Q8. Online TF-IDF
HashingVectorizer + TfidfTransformer。 ただし IDF は再計算。
追補 Q9. 文書類似度の評価
MRR (Mean Reciprocal Rank)、 NDCG (Normalized Discounted Cumulative Gain)。
追補 Q10. 剽窃検出での TF-IDF
n-gram レベル TF-IDF + コサイン類似度。 90% 超で「酷似」判定。
追補 Q11. FAQ マッチング
問合せ ↔ FAQ の TF-IDF コサイン。 Top-3 候補を返す。
追補 Q12. ニュース推薦
ユーザの読み履歴ベクトル + ニュース TF-IDF で類似度推薦。
追補 Q13. 検索ランキング指標
Precision@k、 Recall@k、 MAP、 NDCG。 IR 評価の標準。
追補 Q14. Elasticsearch の利用
match クエリで BM25 自動。 大規模全文検索の定番。
追補 Q15. RAG (Retrieval-Augmented Generation)
TF-IDF/BM25 で文書取得 → LLM で生成。 ChatGPT で文書根拠を提示。
追補 Q16. ベクトル DB
Pinecone、 Weaviate、 Milvus、 Qdrant。 密ベクトル類似検索。
追補 Q17. Sparse vs Dense Retrieval
TF-IDF/BM25 (疎) は解釈容易、 BERT (密) は意味理解。 ハイブリッドが流行。
追補 Q18. ColBERT
BERT を文書 IR 用にチューニング。 トークンレベル類似度。
追補 Q19. SPLADE
BERT で疎ベクトルを生成、 BM25 と互換。 新世代スパース検索。
追補 Q20. TF-IDF を学ぶ書籍
Manning『IR』、 Croft『Search Engines』、 Aggarwal『Machine Learning for Text』。

🌟 SSDSE-B-2026 で「TF-IDF」の総合演習

47 都道府県 × 12 年分のデータを使い、 TF-IDF を多角的に体験する総合演習。 単発の操作ではなく、 一連の分析フローを通して理解を深めます。

ハンズオン 1: SSDSE-B-2026 を TF-IDF で類似県発見

🎯 このコードでやること:SSDSE-B-2026 を TF-IDF で類似県発見

📥 入力:SSDSE-B-2026.csv の 47 都道府県データ。

前述同様のデータフレーム
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
d  = df[df['年度']==2023].reset_index(drop=True)
d['高齢化率'] = (d['65歳以上人口']/d['総人口']*100).astype(int)

docs = []
for _, r in d.iterrows():
    docs.append(f"{r['都道府県']} 人口{r['総人口']//10000}万 出生{r['出生数']//100}百 高齢{r['高齢化率']}")

v = TfidfVectorizer()
M = v.fit_transform(docs)
sim = cosine_similarity(M)

# 各県の TOP3 類似県
for i in range(47):
    s = sim[i].copy()
    s[i] = -1
    top3 = s.argsort()[-3:][::-1]
    cs = ', '.join([d.iloc[j]['都道府県'] for j in top3])
    if i < 5:
        print(f"{d.iloc[i]['都道府県']:<6s} ~ {cs}")

📤 実行結果:

北海道 ~ 宮崎県, 鹿児島県, 福島県 青森県 ~ 山口県, 山形県, 徳島県 岩手県 ~ 愛媛県, 大分県, 長崎県 宮城県 ~ 京都府, 兵庫県, 沖縄県 秋田県 ~ 沖縄県, 東京都, 静岡県

💬 結果の読み方(退化に注意):類似度はほぼ全ペアで 0 か極小で、 上位に出るのは「高齢◯◯」の階級値がたまたま一致した県だけ(例:北海道と宮崎・鹿児島は cos≈0.13、 秋田は非ゼロの相手が無く同率 0 の並びに意味なし)。 「東北は東北と似る」のような地理的近縁性は再現されない点に注意。 本編コード 2 で見た tokenizer 退化(数値が丸ごと 1 トークン化され県間の共有語がほぼ無い)がここでも起きている。 意味のある類似県探索には数値のビン化・トークン分割などの特徴設計が必須。

❌ 誤解「TF-IDF は意味で検索できる」— 言い換えと語順の実測

TF-IDF は語の一致しか見ないので、同じ意味でも違う言葉で書かれた文書は見つけられません。上の指標名コーパスで、言い換えた検索語と語順を入れ替えた検索語を試します。

🎯 このコードでやること:指標名 109 個の TF-IDF 行列に対して「高齢者の人口」「65歳以上人口」「お年寄り」「女 人口」「人口 女」を検索し、語彙に含まれた語と上位 3 件を表示する。

📥 入力データ:上と同じ 109 個の指標名(janome で名詞に分割)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np
import pandas as pd
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer

names = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', nrows=2, header=None).iloc[1, 3:].tolist()
tok = Tokenizer()
def words(s):
    return [t.surface for t in tok.tokenize(s) if t.part_of_speech.startswith('名詞')]
docs = [' '.join(words(s)) for s in names]
vec = TfidfVectorizer(tokenizer=str.split, token_pattern=None, lowercase=False)
X = vec.fit_transform(docs)

def search(query, k=3):
    q = vec.transform([' '.join(words(query))])
    sim = (X @ q.T).toarray().ravel()
    known = [w for w in words(query) if w in vec.vocabulary_]
    hits = [f'{names[i]}({sim[i]:.2f})' for i in np.argsort(-sim)[:k] if sim[i] > 0]
    print(f'「{query}」 語彙にある語 {known} → {hits if hits else "一致なし"}')

search('高齢者の人口')          # 「高齢者」は指標名に無い言い方
search('65歳以上人口')
search('お年寄り')
search('女 人口')               # 語順を入れ替えても
search('人口 女')               # 同じベクトルになる

📤 実行結果:

「高齢者の人口」 語彙にある語 ['者', '人口'] → ['総人口(0.73)', '総人口(男)(0.48)', '総人口(女)(0.48)'] 「65歳以上人口」 語彙にある語 ['65', '歳', '以上', '人口'] → ['65歳以上人口(1.00)', '65歳以上人口(女)(0.90)', '65歳以上人口(男)(0.90)'] 「お年寄り」 語彙にある語 [] → 一致なし 「女 人口」 語彙にある語 ['女', '人口'] → ['総人口(女)(1.00)', '日本人人口(女)(0.80)', '総人口(0.65)'] 「人口 女」 語彙にある語 ['人口', '女'] → ['総人口(女)(1.00)', '日本人人口(女)(0.80)', '総人口(0.65)']

💬 結果の読み方:「高齢者の人口」は、指標名に「高齢」という語が無いため、一致するのは「者」と「人口」だけで、1 位は「総人口」(0.73)になり、目的の「65歳以上人口」は上位に来ません。指標名と同じ言い方の「65歳以上人口」なら 1.00 で一致します。「お年寄り」は語彙に 1 語も無いので一致なしです。また「女 人口」と「人口 女」は結果が完全に同じで、語順の情報は使われていません。言い換えに対応するには、同義語辞書でクエリを展開するか、単語や文の意味をベクトルにする埋め込み(Word2Vec・BERT 系)を使います。

✅ 理解度チェック(指標名コーパスの実測値で答える)

Q1. 109 個の指標名のうち「数」を含むのは 61 個。sklearn の既定(smooth_idf=True)で「数」の IDF を手で計算せよ。
→ 解答:ln((1+109)/(1+61)) + 1 = ln(1.774) + 1 ≈ 0.573 + 1 = 1.573。コードの出力と一致する。

Q2. 「病院数」で検索すると、TF でも TF-IDF でも類似度が 0 より大きい指標名は 61 個だった。なぜ同じ数になるのか。
→ 解答:類似度が 0 より大きくなる条件は「クエリと共有する語が 1 つでもある」ことで、IDF は語の重みを変えるだけで 0 にはしない(smooth_idf では最小でも 1)。したがってヒットする文書の集合は同じで、並び順だけが変わる。

Q3. 「高齢者の人口」で「65歳以上人口」が見つからなかった。TF-IDF のままで見つけられるようにするには、どこを変えればよいか。
→ 解答:クエリ側で「高齢者」を「65 歳 以上」に展開する同義語辞書を入れる。文書とクエリで同じ語が使われて初めて TF-IDF の類似度が生じるので、語彙をそろえる前処理が必要になる。

🗺 概念マップ

TF-IDF を中心に、 単語頻度 (TF) × 逆文書頻度 (IDF) という掛け算が、 BoW・コサイン類似度・文書分類・検索ランキングへどう派生するかを整理。

TF-IDF BM25 Word2Vec / GloVe BERT / SBERT LSA / LDA Sublinear TF コサイン類似度

中心の「TF-IDF」から、 上に BoW (基礎)、 右上に コサイン類似度 (組合せ)、 右下に Word2Vec / BERT (発展)、 下に LSA / LDA (応用)、 左下に Sublinear TF (拡張)、 左上に「落とし穴 (語順無視・OOV)」が並ぶ。 SSDSE-B-2026 の都道府県統計を「文書」、 47 県を「コーパス」と見立てて TF-IDF を計算すると、 各県の特徴語 (北海道なら「畑」「酪農」、 沖縄なら「観光」) を抽出できる。

🔗 隣接手法への橋渡し

TF-IDF は「テキスト → 数値ベクトル」の橋渡し役で、 前処理・後段の手法と直結する。

SSDSE-B-2026 の県別記述文を corpus とした場合、 TF-IDF 行列 (47 県 × 語彙数) を SVD で 50 次元に圧縮 → コサイン類似度 で「東京 ≈ 大阪 ≈ 愛知」を確認、 という pipeline が典型。

🌳 手法選択フロー

テキスト解析で TF-IDF を使うか、 別の表現を選ぶかは 4 つの分岐で決まる。

  1. 語順を考慮したい? Yes → BERT / Transformer 埋め込み、 No → TF-IDF か BoW
  2. 長文文書を比較する? Yes → TF-IDF + L2 正規化、 短文 (検索クエリ vs 文書) → BM25 の方が高精度
  3. 未知語が多い (Twitter ノイズなど)? Yes → subword や FastText、 No → TF-IDF (語彙固定 OK)
  4. 説明性が必要? Yes → TF-IDF (「IDF=4.2 の重み」と語ごとに説明可)、 No → 埋め込み でも可

SSDSE-B-2026 の県別統計コメントのように 定型語彙が多い (n=47, 語彙 ~500) ケースでは TF-IDF が最適。 BERT は overkill で計算コストが過大。

🎮 触って理解する

下のスライダーで 3 つの文書 (A / B / C) における各単語の出現回数を変えると、 TF(語頻度)・ IDF(レア度 ln(N/df))・ TF-IDF = TF × IDF が右の表とバーにリアルタイムで反映されます。 「人口」を 3 文書すべてに入れると IDF が 0 に潰れ、 特定文書に偏る「経済」「高齢」ほどバーが伸びる —— この重み付けを指で体感してください。

① 各文書での単語頻度をスライダーで調整(タップ&ドラッグ対応)

注目する文書:  

② 注目文書に現れる語の TF・IDF・TF-IDF(TF-IDF 降順)

③ TF-IDF バー(大きい語=「その文書らしい」語)— バーに触れると詳細表示

バーに触れると詳細を表示

💡 直感: 「珍しくて(=多くの文書には出ない)、 その文書には何度も出る」語ほど重要。 全文書に出る語(the・です・人口)は IDF = ln(N/df) が 0 に近づき TF-IDF が小さくなる。 単語の単位を変えたいときは n-gram を参照。

⚠️ よくある落とし穴: (1) 正規化 — scikit-learn は既定で L2 正規化するため、 素朴な TF×IDF と絶対値は異なる(大小関係の直感は概ね保たれる)。 (2) ストップワード — 「は・が・the」は IDF で自然に弱まるが、 事前除去でノイズが減る。 日本語では 形態素解析・分かち書き で語に分ける前処理が前提。 (3) 対数の底 — 本デモは自然対数 ln(底 e)。 教科書の log2・log10 やライブラリの平滑化で数値は変わるが、 順位は概ね不変。

🚀 発展: BM25(文書長で TF を飽和させる確率的ランキング。 検索で TF-IDF より高精度・専用ページは未整備)/ 単語の意味を捉える 埋め込み・Transformer(BERT)/ ベクトル化後は コサイン類似度 で テキスト類似度 を測る。 Bag-of-Words(語順を捨てた頻度ベクトル)は TF-IDF の土台となる表現。

❌ ストップワード処理
「は」「が」「the」のような機能語は IDF で弱まりますが、 計算前に除いた方がノイズが減ります。
❌ 対数の底
scikit-learn は log_e、 教科書は log_2 や log_10 が多い。 ライブラリ間で値が違うのは底の違い。
❌ 正規化
scikit-learn はデフォルトで L2 正規化。 「TF を文書長で割った」だけの素朴版とは値が違う。
❌ 文脈を捉えられない
TF-IDF は 順序を無視 (Bag-of-Words)。 「猫が犬を追う」と「犬が猫を追う」が同じベクトルに。
❌ 低頻度語の過剰重要視
1 回しか出ない誤字も IDF が高くなる。 min_df で足切りを。
💥 ストップワード未除去
「の」「は」「が」が高 TF だが IDF も低い→無視される。 ただし日本語は分かち書きが必要。 MeCab + ストップワードリスト併用。
💥 文書長の偏り
長文書は単純 TF が高くなる。 頻度正規化または BM25 で補正。
💥 レアな typo が高 TF-IDF に
typo は 1 文書のみ出現で IDF 大、 TF も 1 で値が異常に高い。 min_df=2 などで除去。
💥 英大文字小文字
デフォルトの sklearn は lowercase 変換。 固有名詞 (Apple vs apple) で意味が変わる場合は注意。
💥 単語分割の質
日本語は MeCab・SudachiPy の辞書次第で「東京」「東京都」が別単語に。 一貫した分割が必須。
⚠️ 日本語のトークナイズ忘れ
デフォルトは英語の空白区切り。 MeCab/Sudachi が必須。
⚠️ min_df が緩すぎ
min_df=1 だと typo まで含まれノイズ。 通常 min_df=2 以上。
⚠️ max_df 設定なし
「は」「の」が IDF 低で残る。 max_df=0.95 で除去。
⚠️ 大文字小文字
デフォルトは lowercase。 固有名詞 (Apple) が apple に。
⚠️ ハイフン語
token_pattern で英数字+ハイフンを許可しないとちぎれる。
⚠️ 巨大語彙
10 万語超で OOM。 max_features=10k〜50k で打ち切り。
⚠️ L1 正規化
デフォルトの L2 が cos 類似度に最適。 L1 は文書長に弱い。
⚠️ テストデータも fit
fit_transform をテストにも適用するとリーク。 train で fit、 test は transform のみ。
⚠️ BM25 と TF-IDF 混同
BM25 は飽和あり、 文書長正規化あり。 計算式が異なる。
⚠️ 文書埋め込みと混同
TF-IDF は疎、 BERT は密。 用途で使い分け。

📜 ひとことヒストリー

TF-IDF は「NLP」分野の中で発展してきた概念・手法です。 学術的には継続的な研究で精緻化され、 実務的にはツール・ライブラリの普及で誰でも使えるようになってきました。 用語の使い方・意味は時代と分野で少しずつ変わるため、 文脈に応じた解釈が大切です。 入門書だけでなく、 標準的な教科書(例:データサイエンス・統計学の定本)や信頼できるオンライン教材も併用すると、 ぶれない理解に近づけます。

✅ 実務チェックリスト — TF-IDF

  • □ 用語の定義を自分の言葉で説明できるか
  • □ 使うべき場面と使ってはいけない場面を区別できているか
  • □ 数式や指標の前提条件を確認したか
  • □ 入力データの尺度・分布・サンプル数を確認したか
  • □ 結果の不確実性(信頼区間・標準誤差)を把握しているか
  • □ 解釈と限界を区別できているか
  • □ 関連用語・落とし穴を一通り点検したか
  • □ レポートに必要な情報(出典・前提・限界)を含められるか

🎯 まとめ — このページで押さえること

「TF-IDF」 はこのページで詳しく扱った概念です。 持ち帰ってほしい 3 つの要点:

  1. TF-IDF=「ある文書で頻出するが、 他の文書ではあまり出ない単語」を重要と判定するスコア。
  2. TF(Term Frequency)=その文書での出現頻度、 IDF(Inverse Document Frequency)=レア度。 掛け算で重要度に。
  3. 「the」「です」のような頻出語は IDF が小さくなり、 自動的に重要度ダウン。

さらに学ぶには、 関連用語 や 関連グループ教材 を参照してください。 各用語ページを縦断的に読むことで、 体系的な理解が育ちます。

🔍 解説深化 — IDF は「文章専用」ではない:構造化データで観る TF-IDF の本質

本編では都道府県データを「プロフィール文書」に変換して TF-IDF を適用しました。 ここでは逆向きの角度から掘り下げます。 文章を一切使わずに、 SSDSE-B-2026 の数値そのものから「トークン」を作り、 IDF という考え方の正体(=珍しさの対数)と、 文章では見えにくい弱点を露出させます。

💡 直感 — IDF の正体は「驚きの対数」

IDF(t) = log(N / df(t)) は、 「N 文書から 1 つ引いたとき、 語 t を含む文書を引き当てる確率 df/N」の逆数の対数、 つまり情報理論でいう 自己情報量(サプライザル)−log(df/N) そのものです。 「みんなが持っている語は驚きゼロ、 少数派だけが持つ語は驚き大」。 この見方に立つと、 トークンは単語である必要がない ことに気づきます。 「ある性質を持つ/持たない」という 0/1 のメンバーシップさえ定義できれば、 どんなデータにも IDF は計算できます。

実験:SSDSE-B-2026 の 2023 年・47 都道府県 × 数値 109 列で、 「変数 X が全国平均を上回る」ことを 1 トークンとみなします(47 文書 × 109 語彙の 0/1 行列)。 以下は Python で実際に計算した 実測値 です(IDF は自然対数)。

トークン(平均超えの変数)df(該当県数)IDF = ln(47/df)
外国人延べ宿泊者数(G7102)81.77(最レア級)
着工新設分譲住宅数(H1803)81.77
総人口(A1101)121.37
年平均気温(B4101)260.59
消費支出・二人以上世帯(L3221)280.52(最コモン)

「外国人延べ宿泊者数が平均超え」なのは北海道・千葉・東京・神奈川・京都・大阪・福岡・沖縄の 8 道都府県だけ。 このトークンを持つことは県の性格を強く語る(IDF 高)のに対し、 「消費支出が平均超え」は 28 県が持つため、 ほとんど個性を語りません(IDF 低)。 ストップワードが自動的に軽くなる仕組みと完全に同型です。

⚠️ 落とし穴(重要) — 文章では気づきにくい IDF の 3 つの弱点

① 小さいコーパスでは IDF のダイナミックレンジが潰れる。 N=47 のとき IDF の理論上の最大値は ln(47/1) = 3.85、 上の実測でも 0.52〜1.77 の幅しかありません。 数千〜数百万文書を前提に設計された IDF は、 文書数が少ないと「レア語ボーナス」がわずかしか付かず、 重み付けの効果が薄れます。 小規模データで TF-IDF の効きが悪いのは実装ミスではなく数学的必然です。

② トークン化(離散化)の設計ひとつで IDF は崩壊する。 上の実験でしきい値を「平均」から「中央値」に変えると、 df は必ず半数近く(実測で 21〜23 県)になり、 IDF は 0.715〜0.806 の狭い帯に 全 109 変数が押し込まれて しまいます。 珍しさの情報が消え、 全語が同じ重みに。 テキストでも同じで、 形態素解析の粒度・ストップワード辞書・n-gram の切り方という「トークン化の設計」が、 数式より先に結果を決めます。

③ 「長い文書」問題は構造化データにも現れる。 平均超えトークンの保有数を数えると、 東京都は 109 変数中 102 個、 福岡県 101 個、 神奈川県・愛知県 99 個。 一方で愛媛県はわずか 3 個(合計特殊出生率・年平均気温・最低気温)、 青森県 4 個、 秋田県 5 個でした。 SSDSE-B の多くが人口規模に比例する「総量」変数のため、 大きな県が何でも持つ「長文書」になるのです。 長い文書ほどあらゆる語を含んでしまい類似度計算を支配する——TF の文書長正規化や BM25 のパラメータ b が解決しようとしているのは、 まさにこの規模効果です。 都道府県分析なら「人口あたり」に直してから比較するのが定石です。

🚀 発展 — IDF 的発想が再登場する場所

🔗 関連ページ

※ 本節の数値はすべて SSDSE-B-2026(2023 年・47 都道府県・数値 109 列)からの実測値です(IDF は自然対数)。 合成データ・仮想例は使用していません。