はじめに
近年、GeminiやChatGPTをはじめとする生成AI(Generative AI)が目覚ましい発展を遂げ、私達の日常やビジネスにおいて、テキスト生成、要約、翻訳など、多岐にわたるタスクをこなすようになりました。これらの生成AIの場合、大量のデータからパターンを学習するディープラーニング技術を使用してテキスト生成などを行っています。
一方自然言語処理(NLP)では、別のアプローチとしてAIによる高度な解析技術が登場する以前から、テキストデータに含まれる情報を効率的に抽出し、その特徴を掴むための統計的な手法が存在します。
その中でも、文書の中から「その文書を最も特徴づけている語句」を、他の文書と比較しながら客観的に判断する仕組みで現在も広く利用されているものとしてTF-IDF(Term Frequency-Inverse Document Frequency)が挙げられます。
本記事では、このTF-IDFの基本的な概念の解説及び、Pythonを利用した実装例を紹介します。
TF-IDFについて
TF-IDFは、「その文書を最も特徴づけている語句」を、他の文書と比較しながら客観的に判断する仕組みであり、TF(単語頻度)とIDF(逆文書頻度)の2つの要素から構成されています。
1. TF(Term Frequency:単語頻度)
TFは、ある文書の中に、特定の単語 $t$ がどれだけ多く出現するかを示す指標です。
単語 $t$ が文書にとってどれだけ重要そうかを表しますが、「〜は」「です」などの一般的な単語のスコアも高くなってしまうという問題があります。
-
計算式
$$
\mathrm{TF}(t, d) = (\text{文書 } d \text{ における単語 } t \text{ の出現回数})
$$
2. IDF(Inverse Document Frequency:逆文書頻度)
IDFは、ある単語が、全文書の中でどれだけの文書に出現しているか(文書頻度:DF)の逆数を取ることで、その単語の希少性を示す指標であり、一般的な単語のスコアが高くなってしまうTFの問題点を補います。
-
計算式(一般的なもの):
$$
\mathrm{IDF}(t, D) = \log \left( \frac{|D|}{\mathrm{DF}(t)} \right) + 1
$$- $|D|$: 全文書の数(コーパスサイズ)
- $\mathrm{DF}(t)$: 単語 $t$ が出現する文書の数(Document Frequency)
3. TF-IDFスコアの計算
TFとIDFを掛け合わせることで、単語の重要度を計算します。
-
計算式:
$$
\mathrm{TF-IDF}(t, d, D) = \mathrm{TF}(t, d) \times \mathrm{IDF}(t, D)
$$
実装例
日本語の形態素解析ライブラリ Janome を使用して、外部ファイルから読み込んだテキストデータから重要語を抽出する実装例を紹介します。
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from janome.tokenizer import Tokenizer
# Janomeの初期化
t = Tokenizer()
def get_top_tfidf(corpus_file, target_file, top_n=5):
# 1. データの準備
with open(corpus_file, 'r', encoding='utf-8') as f:
corpus = [line.strip() for line in f if line.strip()]
with open(target_file, 'r', encoding='utf-8') as f:
target = f.read().strip()
# 2. Vectorizerの設定:日本語を単語単位に分割するルールを定義
# lambda内で「形態素解析 -> 表層形(単語そのもの)のリスト化」を実施
vectorizer = TfidfVectorizer(tokenizer=lambda x: [tok.surface for tok in t.tokenize(x)], token_pattern=None)
# 3. TF-IDFの計算実行
# fit_transformにより以下の計算が行われる:
# TF (Term Frequency): その文書内での単語の出現頻度(高いほど重要)
# IDF (Inverse Document Frequency): 全文書中での単語の珍しさ(多くの文書に出る語は低スコア)
# TF × IDF = 各単語の最終的な重要度スコア
tfidf_matrix = vectorizer.fit_transform(corpus + [target])
# 4. 特徴量名(単語一覧)の取得
feature_names = vectorizer.get_feature_names_out()
# 5. ターゲット文書(行列の最後の一行)のスコアを抽出
# tfidf_matrix.toarray()[-1] で「ターゲット文書における全単語のTF-IDFスコア」が数値配列で取得できる
target_vector = tfidf_matrix.toarray()[-1]
# 6. スコアと単語を結びつけてソート(並び替え)
# pd.Seriesで「単語: スコア」の対応表を作り、sort_values(ascending=False)でスコアの高い順に並び替える
scores = pd.Series(target_vector, index=feature_names).sort_values(ascending=False)
# 上位n個を返す
return scores.head(top_n)
if __name__ == "__main__":
try:
print("--- ターゲット文書の重要語(TF-IDFスコア順) ---")
# 計算結果:1に近いほどその文書に特有の重要な単語、0に近いほど平凡な単語
print(get_top_tfidf("corpus_data.txt", "target_document.txt"))
except Exception as e:
print(f"エラーが発生しました: {e}")
上記ソースコードでは、比較対象となる「コーパス(母集団)」が記載されたcorpus_data.txtと、解析したい「ターゲット文書」であるtarget_document.txtを読み込み、TfidfVectorizerによってTF-IDFスコアの算出を行っています。
例えば、母集団を以前執筆した以下の記事、ターゲット文書を本記事の『TF-IDFについて』章にした場合の上記ソースコードの実行結果は以下になります。
--- ターゲット文書の重要語(TF-IDFスコア順) ---
文書 0.395372
tf 0.263581
idf 0.263581
頻度 0.263581
( 0.225208
dtype: float64
まとめ
本記事ではTF-IDFを使用して文中から重要度の高い語の抽出を行いました。
今回の実装例では、形態素解析によって形態素単位で分割した語をそのまま使用してスコアの計算を行っているため、『単語頻度』などの複数の形態素が合わさった複合語を1つの単語として認識できずに『単語』『頻度』の2語していたり、一般的な記号の『(』を重要語として挙げていたりする結果になりました。
そのため、実際にTF-IDFを使用して重要度を求める際には、スコアの計算方法だけでなく、その前の形態素解析の方法(形態素解析後、品詞の情報などを元に形態素から単語に変換する。副詞など、特定の品詞はスコア算出の対象外にする等)を考慮する必要があると考えられます。