Pythonによるテキストキーワード/キーフレーズ抽出実践ガイド
はじめに
インターネットの普及やデジタル化の進展により、私たちが扱うテキストデータは日々増加しています。顧客レビュー、問い合わせログ、技術文書、社内レポートなど、様々な非構造化テキストの中に、業務にとって価値のある情報が埋もれています。これらの情報から効率的に、かつ漏れなく必要な要素を抽出することは、データ分析や意思決定において非常に重要となります。
しかし、膨大なテキストデータを人手で全て読み込み、要点を把握したり重要な情報を特定したりすることは現実的ではありません。ここで、自然言語処理(NLP)の技術が役立ちます。特定の情報を抽出する手法の一つとして、「重要語抽出」や「キーワード/キーフレーズ抽出」があります。これは、文書の中からその内容を代表する、あるいは特筆すべき単語やフレーズを自動的に見つけ出す技術です。
本記事では、Pythonを用いて、テキストデータから重要語(キーワードやキーフレーズ)を抽出するための実践的な手法を解説します。具体的なアルゴリズムの考え方から、Pythonライブラリを使った実装例、そして実務への応用方法やシステム構築上の考慮点までを網羅し、読者の皆様がご自身の業務に本技術を適用するための一助となることを目指します。
重要語抽出とは
重要語抽出(Keyword/Keyphrase Extraction)とは、与えられたテキスト文書から、その文書の主要なトピックや内容を最もよく表す単語や単語列(フレーズ)を自動的に特定する技術です。抽出された語は、文書の要約、タグ付け、検索システムにおけるインデックス作成、トピック分析、レコメンデーションシステムなど、様々な用途で活用されます。
重要語抽出の手法は多岐にわたりますが、大きく分けて以下のカテゴリに分類できます。
- 統計的手法: 単語の出現頻度や文書間の出現分布といった統計的な指標に基づいて重要度を算出する手法です。TF-IDFなどが代表的です。比較的実装が容易で計算コストも低いですが、単語単体の重要度を測る傾向が強いです。
- グラフベース手法: 文書内の単語やフレーズ間の関係性をグラフ構造で表現し、グラフアルゴリズム(PageRankなど)を用いて重要度を算出する手法です。TextRankなどが代表的です。単語間の文脈的な繋がりを考慮しやすいですが、統計的手法に比べて計算コストが高くなる場合があります。
- 教師あり学習手法: 事前に人手で重要語がアノテーションされたデータセットを用いてモデルを学習させ、未知のテキストから重要語を予測する手法です。高い精度が期待できますが、アノテーション付きデータセットの準備が必要です。
本記事では、教師なしアプローチである統計的手法からTF-IDF、グラフベース手法からTextRankを取り上げ、それぞれの考え方とPythonによる実装に焦点を当てます。これらの手法は、アノテーションデータを必要とせず、比較的容易に導入できるため、テキスト分析の第一歩として適しています。
前処理の重要性:日本語テキストの場合
日本語の重要語抽出を行う上で、前処理は非常に重要です。英語のように単語がスペースで区切られている言語と異なり、日本語は単語が連続して記述されるため、「分かち書き」、すなわち単語の区切りを特定する「形態素解析」が必要です。
形態素解析では、テキストを「形態素」(意味を持つ最小単位の言葉)に分割し、それぞれの形態素に対して品詞などの情報を付与します。これにより、「すもももももももものうち」という文が「すもも/名詞, も/助詞, もも/名詞, も/助詞, もも/名詞, の/助詞, うち/名詞」のように解析され、各単語を単位として扱うことが可能になります。
また、重要語抽出においては、文書の内容を表す上で重要ではない単語(「て」「に」「を」「は」のような助詞、一般的な動詞や形容詞など)を除外することも一般的です。これらは「ストップワード」と呼ばれます。さらに、表記のゆれ(例:「スマホ」「スマートフォン」)を統一する正規化処理も、抽出精度向上に貢献します。
Pythonで日本語の形態素解析を行うには、MeCabやJanome、SpaCy(日本語モデル利用時)といったライブラリが利用できます。ここでは、比較的導入が容易なJanomeを例に、簡単な形態素解析のイメージを示します。
from janome.tokenizer import Tokenizer
# Janomeのトークナイザーを初期化
t = Tokenizer()
text = "この製品は使い方が簡単で、機能も豊富です。"
# 形態素解析を実行
tokens = t.tokenize(text)
# 結果を表示 (単語/品詞 の形式で)
for token in tokens:
print(f"{token.surface}/{token.part_of_speech}")
# 抽出したい単語(名詞、形容詞、動詞など)だけを取得する例
important_words = [token.surface for token in tokens if token.part_of_speech.split(',')[0] in ['名詞', '形容詞', '動詞']]
print(f"抽出された重要そうな単語: {important_words}")
上記の例では、形態素解析によって単語に分割され、品詞情報を取得できています。実際の重要語抽出では、このようにして得られた単語リストや、品詞、原形などの情報を基に処理を進めます。
手法1:統計的手法(TF-IDF)
TF-IDF(Term Frequency-Inverse Document Frequency)は、情報検索やテキストマイニングで広く用いられる、単語の重要度を測るための指標です。ある単語が特定の文書内でどれだけ頻繁に出現するか(TF)、そして文書集合全体の中でどれだけ珍しいか(IDF)を考慮します。
- TF (Term Frequency): 特定の文書内での単語の出現頻度です。単語 t が文書 d に出現する回数を、文書 d 中の全単語数で割るなど、いくつかの定義方法がありますが、単純に回数自体を用いることもあります。文書内でよく出てくる単語ほどTFは高くなります。
- IDF (Inverse Document Frequency): 単語の希少性を示します。単語 t が出現する文書が少ないほどIDFは高くなります。文書集合 D 内の全文書数 N を、単語 t が出現する文書数 $n_t$ で割った値の対数として定義されることが多いです。 $IDF(t, D) = \log(\frac{N}{n_t})$ IDFは、多くの文書に共通して出現する単語(例:「〜である」「〜する」)の重要度を低く評価し、特定の文書にのみ出現する単語の重要度を高く評価する効果があります。
TF-IDFスコアは、TFとIDFの積として計算されます。 $TF-IDF(t, d, D) = TF(t, d) \times IDF(t, D)$
TF-IDFスコアが高い単語は、特定の文書内で比較的よく出現し、かつ文書集合全体ではあまり出現しない、つまりその文書を特徴づける可能性の高い単語とみなすことができます。
Pythonのscikit-learnライブラリには、TF-IDFを計算するための TfidfVectorizer
が用意されており、手軽に利用できます。TfidfVectorizer
は、テキストデータを受け取り、内部で語彙の構築(どの単語が出現するか)、TF-IDFの計算、そしてそれをベクトル形式に変換する処理を行います。日本語を扱う際は、前処理として形態素解析を行い、分かち書きされた単語リストを入力として与える必要があります。
以下に、scikit-learnを使ったTF-IDFによる重要語抽出の例を示します。ここでは簡単のため、分かち書き済みの単語リスト(スペース区切り)を入力として与えることを想定します。
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
# サンプル文書集合 (形態素解析・分かち書き済みを想定)
corpus = [
"製品 使い 方 簡単 機能 豊富",
"価格 少し 高い 感じ た",
"使い やすい デザイン 良い 大変 満足",
"機能 多い が 操作 難しい",
"サポート 良い 問題 すぐ 解決 し た"
]
# TfidfVectorizer を初期化
# stop_words='english' は英語用なので日本語には不向き。
# 日本語の場合は独自のストップワードリストを指定するか、前処理で除去する。
# ここでは簡単のため指定しない。
vectorizer = TfidfVectorizer()
# 文書集合に対してフィットさせ、TF-IDF行列を生成
tfidf_matrix = vectorizer.fit_transform(corpus)
# 語彙(単語)リストを取得
feature_names = vectorizer.get_feature_names_out()
# 各文書のTF-IDFスコアを表示
# ここでは最初の文書に焦点を当て、上位の単語とそのスコアを表示する
doc_index = 0
doc_tfidf_scores = tfidf_matrix[doc_index]
# スコアを単語とペアにしてDataFrameに変換
df_tfidf = pd.DataFrame(doc_tfidf_scores.T.todense(), index=feature_names, columns=[f'Doc{doc_index}'])
# スコアの高い順にソートして表示 (例: 上位5件)
print(f"文書 '{corpus[doc_index]}' のTF-IDF上位単語:")
print(df_tfidf.sort_values(by=f'Doc{doc_index}', ascending=False).head())
print("\n---")
# 別の文書 (例: 文書2) のTF-IDF上位単語
doc_index = 2
doc_tfidf_scores = tfidf_matrix[doc_index]
df_tfidf = pd.DataFrame(doc_tfidf_scores.T.todense(), index=feature_names, columns=[f'Doc{doc_index}'])
print(f"文書 '{corpus[doc_index]}' のTF-IDF上位単語:")
print(df_tfidf.sort_values(by=f'Doc{doc_index}', ascending=False).head())
上記のコードでは、TfidfVectorizer
が自動的にコーパス内の単語から語彙を作成し、各文書におけるTF-IDFスコアを計算します。各文書のTF-IDFスコアが高い単語が、その文書を特徴づける重要語として抽出されます。TF-IDFは単語ベースの手法であり、キーフレーズ(複数の単語からなる句)を直接抽出するには、N-gram(連続するN個の単語列)をTF-IDFの対象とするなどの工夫が必要です。
手法2:グラフベース手法(TextRank)
TextRankは、文書内の単語や文の重要度をグラフ構造とPageRankアルゴリズムを用いて算出する手法です。単語間の共起関係をエッジとするグラフを構築し、単語の重要度をPageRankスコアとして計算します。重要語抽出の場合、通常は単語グラフを構築します。
- 単語グラフの構築:
- 文書を単語に分割します(形態素解析済み単語リストを使用)。
- 一定のウィンドウサイズ(例:前後N単語)内で共起する単語間にエッジを張ります。共起回数をエッジの重みとすることも可能です。
- 各単語をグラフのノードとします。
- PageRankアルゴリズムの適用:
- 構築した単語グラフに対し、PageRankアルゴリズムを適用します。PageRankは、リンク構造に基づいてノードの重要度を計算するアルゴリズムで、Webページのランキングなどに用いられています。ここでは、共起関係を通じて他の重要な単語と多く繋がっている単語ほど、自身の重要度も高いとみなされます。
- 各単語のPageRankスコアを計算し、スコアの高い順にランキングします。
TextRankは単語間の繋がりを考慮するため、TF-IDFに比べて文脈を反映した重要語を抽出しやすいとされます。また、文書集合全体ではなく、単一の文書に対しても適用可能です。
PythonでTextRankを実装するためのライブラリとしては、gensimやPyTextRankなどがあります。gensimの summarization.keywords.keywords
関数は、TextRankベースのキーワード抽出機能を提供しており、手軽に利用できます。
以下に、gensimを使ったTextRankによる重要語抽出の例を示します。gensimのTextRank機能は、内部で英語のトークナイザやストップワードリストを使用するため、日本語に適用するには工夫が必要です。通常は、事前に形態素解析・ストップワード除去を行い、得られた単語リストをスペース区切りの文字列としてgensimに渡すか、gensimのカスタマイズ機能を利用します。ここでは、簡便のため分かち書き済みテキストを入力とします。
# gensimがインストールされていない場合は pip install gensim でインストール
from gensim.summarization.keywords import keywords
# サンプル文書 (単一の文書に対して適用可能)
text = "この製品は使い方が簡単で、機能も豊富です。しかし、価格が少し高いと感じました。使いやすいデザインも大変気に入っていますし、サポートも非常に良く、問題がすぐに解決しました。全体的には大変満足しています。"
# テキストを分かち書き済みと仮定 (ここでは手動でスペースを挿入)
# 実際には形態素解析結果をスペース区切り文字列に変換する
text_wakati = "この 製品 は 使い 方 が 簡単 で 機能 も 豊富 です しかし 価格 が 少し 高い と 感じ まし た 使い やすい デザイン も 大変 気に入って い ます し サポート も 非常 に 良く 問題 が すぐ に 解決 し まし た 全体 的 に は 大変 満足 し て い ます"
# gensimのTextRankキーワード抽出を実行
# words=True で単語ベースの抽出を指定 (デフォルトはキーフレーズも含む)
# lemmatize=False は英語のレンマ化を行わない設定
# ratio=None, scores=True でスコア付きの単語リストを取得
# split=True でリスト形式で取得
# pos_filter=[] で品詞フィルタリングを無効にする(日本語形態素解析の結果に依存するため)
extracted_keywords = keywords(text_wakati, words=True, lemmatize=False, ratio=None, scores=True, split=True, pos_filter=[])
print("TextRankによる重要語 (単語) とスコア:")
for keyword, score in extracted_keywords:
print(f"{keyword}: {score:.4f}")
print("\n---")
# キーフレーズ抽出 (デフォルト設定に近い形で)
# split=False でカンマ区切りの文字列として取得
extracted_keyphrases = keywords(text_wakati, words=False, lemmatize=False, ratio=None, scores=False, split=False)
print("TextRankによる重要語 (キーフレーズ):")
print(extracted_keyphrases)
TextRankは、単語間の共起関係からグラフを構築するため、単語単体の頻度だけでなく、どのような単語とよく共起するかも考慮して重要度を算出します。上記の例では、「使い」「機能」「価格」「サポート」「問題」「解決」「満足」といった、文書の内容をよく表す単語が上位に来ることが期待されます。また、デフォルト設定では複数の単語を組み合わせたキーフレーズも抽出可能です。
実際の実装例と比較
ここでは、より現実的なレビューデータを想定し、前処理(形態素解析、ストップワード除去)を含めたTF-IDFとTextRankの実装例を示し、それぞれの結果を比較します。使用するライブラリはJanome、scikit-learn、gensimです。
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer
from gensim.summarization.keywords import keywords
import pandas as pd
# Janomeのトークナイザーを初期化
t = Tokenizer()
# ストップワードリスト (例)
stop_words = set([
'て', 'に', 'を', 'は', 'が', 'の', 'と', 'より', 'から', 'まで', 'で', 'も', 'や', 'か', 'な', 'こと', 'もの', 'など',
'られ', 'いる', 'する', 'ませ', 'ます', 'です', 'あり', 'ます', 'なる', 'よう', 'れる', 'られる', 'せる', 'させる',
'やすい', 'にくい', 'そう', 'らしい', 'っぽい', 'みたい', 'です', 'ます', 'ありません', 'ました', 'ください', '〜', '(', ')',
# その他、一般的な単語や記号など必要に応じて追加
])
# 前処理関数: 形態素解析とフィルタリング
def preprocess(text):
tokens = t.tokenize(text)
# 名詞、形容詞、動詞の原形を抽出し、ストップワードを除去
important_tokens = [
token.base_form if token.base_form != '*' else token.surface
for token in tokens
if token.part_of_speech.split(',')[0] in ['名詞', '形容詞', '動詞']
and (token.base_form if token.base_form != '*' else token.surface) not in stop_words
]
return " ".join(important_tokens) # 分かち書き済み文字列として返す
# サンプルレビューデータ
reviews = [
"この製品の使いやすさには感動しました。設定も簡単で、すぐに利用開始できました。機能も非常に豊富で、期待以上のパフォーマンスです。",
"価格は正直少し高いと感じますが、品質を考慮すると納得できます。デザインもシンプルで部屋に馴染みます。",
"購入して大満足です。特にサポートの対応が素晴らしく、問い合わせに対して迅速かつ丁寧な回答を得られました。問題解決までスムーズでした。",
"機能は多いのですが、一部の操作が直感的ではなく、慣れるまで時間がかかりました。マニュアルをしっかり読む必要があります。",
"バッテリーの持ちが悪いです。一日使うと充電が必要です。それ以外は概ね満足しています。",
"コンパクトで持ち運びやすいです。旅行先でも活躍しそうです。軽さも気に入っています。"
]
# 前処理を実行し、分かち書き済みコーパスを作成
processed_corpus = [preprocess(review) for review in reviews]
print("--- 前処理結果 (分かち書き済み) ---")
for i, doc in enumerate(processed_corpus):
print(f"文書{i}: {doc}")
print("---")
# --- TF-IDFによる抽出 ---
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(processed_corpus)
feature_names = vectorizer.get_feature_names_out()
print("--- TF-IDFによる重要語抽出 ---")
# 各文書の上位キーワードを表示
for doc_index, doc in enumerate(processed_corpus):
doc_tfidf_scores = tfidf_matrix[doc_index]
df_tfidf = pd.DataFrame(doc_tfidf_scores.T.todense(), index=feature_names, columns=[f'Doc{doc_index}'])
sorted_keywords = df_tfidf.sort_values(by=f'Doc{doc_index}', ascending=False).head(5)
print(f"文書{doc_index} 上位キーワード: {list(sorted_keywords.index.values)}")
print("\n--- TextRankによる抽出 ---")
# TextRankによるキーワード抽出 (単語ベース, 各文書)
for doc_index, doc in enumerate(processed_corpus):
# TextRankは単一文書を入力とするため、個別に処理
# gensimのkeywords関数は空白区切り文字列を想定
extracted_keywords_tr = keywords(doc, words=True, lemmatize=False, ratio=None, scores=False, split=True, pos_filter=[])
# TF-IDFと比較するため、上位5件程度を表示
print(f"文書{doc_index} 上位キーワード (TextRank): {extracted_keywords_tr[:5]}")
print("\n--- TextRankによるキーフレーズ抽出 ---")
# TextRankによるキーフレーズ抽出 (各文書)
for doc_index, doc in enumerate(processed_corpus):
extracted_keyphrases_tr = keywords(doc, words=False, lemmatize=False, ratio=None, scores=False, split=False)
# 結果はカンマ区切り文字列
print(f"文書{doc_index} キーフレーズ (TextRank): {extracted_keyphrases_tr}")
上記の実行結果を比較すると、TF-IDFは文書集合全体での出現頻度と希少性を考慮するため、文書間で共通する語(例: 製品、機能)よりも、その文書固有の語(例: 価格、サポート、バッテリー)に高いスコアを与えがちです。一方、TextRankは単語間の共起関係を重視するため、文書内で繋がりを持って頻繁に出現する語や、多くの語と共起する語に高いスコアを与えます。キーフレーズ抽出も可能なTextRankは、より具体的な内容を示す語句を捉えるのに役立ちます。
どちらの手法が優れているというよりは、目的とするタスクやテキストデータの性質によって使い分けることが重要です。
実務への応用とシステム設計のヒント
重要語抽出技術は、様々な実務課題に適用可能です。
- 顧客レビュー/アンケートの分析:
- 大量のレビューから、製品やサービスの「良い点」「悪い点」を代表するキーワードを抽出します。これにより、顧客の声を短時間で俯瞰的に把握できます。
- 特定のキーワードの出現傾向を時系列で追うことで、品質改善やマーケティング施策の効果測定にも利用できます。
- FAQ/ドキュメントの自動タグ付け:
- 新しいFAQや技術ドキュメントが作成された際に、自動的に内容に関連するキーワードを抽出し、タグとして付与します。これにより、ドキュメントの分類・整理が効率化され、ユーザーが必要な情報を探しやすくなります。
- 社内文書検索の精度向上:
- 社内Wikiやファイルサーバのドキュメントに対して重要語をインデックス化しておくことで、キーワード検索の精度を向上させます。
- ログ分析:
- システムログやエラーメッセージから、異常を示すキーワードやパターンを抽出します。これにより、障害の早期発見や原因特定の効率化に貢献できます。
システムへ組み込む際の考慮事項としては、以下のような点が挙げられます。
- 処理速度: 大規模なテキストデータ(数百万件のレビューなど)を扱う場合、処理速度がボトルネックになる可能性があります。TF-IDFは比較的計算量が少ないですが、Vectorizerのfit処理はメモリを消費します。TextRankはグラフ構築やPageRank計算に時間がかかる場合があります。バッチ処理で行うか、リアルタイム処理が必要かによって、処理方法やインフラを検討する必要があります。
- スケーラビリティ: 処理対象のテキスト量が増加した場合にスケールできる設計が必要です。分散処理フレームワーク(例: Spark)との連携や、クラウドサービス(AWS Batch, GCP Dataflowなど)の活用が有効です。
- 前処理パイプライン: 形態素解析、ストップワード除去、正規化といった前処理は重要語抽出の精度に直結します。安定した品質の前処理を、効率的に実行できるパイプラインとして構築することが求められます。形態素解析器の選定や辞書登録も重要な検討事項です。
- モデルの更新: 新しいドメインのテキストや流行語などに対応するため、使用する語彙リストやストップワードリスト、あるいはTF-IDFの場合はVectorizerを定期的に更新する必要がある場合があります。
- 抽出結果の評価: 抽出された重要語が本当に有用であるかを評価するための基準を設けることが重要です。タスクによっては、人手による評価や、その後のタスク(検索精度、要約の質など)での評価が必要になります。
考慮すべき点と限界
重要語抽出技術は強力ですが、いくつかの考慮すべき点と限界があります。
- 前処理の品質依存性: 形態素解析の精度やストップワードリストの適切さが、抽出結果に大きく影響します。特にドメイン固有の専門用語が多いテキストを扱う場合は、カスタム辞書の導入などを検討する必要があります。
- 文脈理解の限界: TF-IDFやTextRankのような統計的/グラフベースの手法は、単語間の表面的な共起や頻度に基づいて重要度を判断します。より深い意味合いや文脈を理解した上での重要語抽出は難しい場合があります。例えば、皮肉や比喩を含むテキストから真の重要語を抽出するには、より高度なNLP技術(感情分析や意味解析など)との組み合わせが必要になることがあります。
- 「重要性」の定義: 何をもって「重要」とするかは、タスクや目的によって異なります。これらの手法で抽出されるのは、あくまで「文書を統計的/構造的に特徴づける語」であり、それが必ずしも人間にとっての「重要語」と一致するとは限りません。抽出結果をそのまま利用するだけでなく、必要に応じてフィルタリングやランキングの調整、あるいは人手でのレビューを組み合わせることも重要です。
- キーフレーズ抽出の課題: TextRankはキーフレーズ抽出も可能ですが、適切なキーフレーズの粒度(単語数)や連結ルール(どのような単語の並びをフレーズとみなすか)の設定は容易ではありません。
まとめ
本記事では、テキストデータから重要語(キーワード/キーフレーズ)を抽出するための実践的な手法として、統計的手法であるTF-IDFとグラフベース手法であるTextRankを紹介しました。それぞれの基本的な考え方、Pythonライブラリ(scikit-learn, gensim)を用いた実装方法、そして実務への応用例やシステム設計上の考慮点について解説しました。
- TF-IDFは、単語の文書内での出現頻度と文書集合全体での希少性を組み合わせた指標であり、比較的計算が容易で、文書を特徴づける単語の特定に有効です。
- TextRankは、単語間の共起関係をグラフ化し、PageRankアルゴリズムで重要度を算出するため、単一文書内での文脈を考慮した抽出やキーフレーズ抽出にも適しています。
どちらの手法も一長一短があり、対象とするテキストデータや抽出の目的に応じて適切に選択、あるいは組み合わせて利用することが重要です。また、日本語テキストを扱う上では、形態素解析をはじめとする前処理が不可欠であり、その品質が抽出結果に大きく影響することを理解しておく必要があります。
今回紹介した手法は、重要語抽出の基本的なアプローチです。より高度なタスクやドメイン特化のニーズに対しては、固有表現抽出、関係抽出、トピックモデリング、あるいは事前学習済み言語モデル(BERTなど)を用いた手法など、さらに様々なNLP技術が存在します。
これらの基本手法を習得し、ご自身の業務におけるテキストデータ分析の効率化にぜひ役立てていただければ幸いです。