TextRankは、PageRankから派生したグラフベースの自然言語処理アルゴリズムであり、単語や文をノード、それらの共起・類似関係をエッジとして構築されたネットワーク上で反復的に重要度(スコア)を計算します。この手法は、TF-IDFとは異なり、文書内の局所的およびグローバルな構造情報を活用して、意味的に重要な単語や文を自動抽出します。
本実装では、中国語テキストを対象に、textrankパッケージとjiebaRを組み合わせて以下の2つのタスクを遂行します:
- キーワード抽出:名詞中心の2-gramまでを考慮し、出現頻度とn-gram長を複合基準でランキング
- 文書要約(キーセンテンス抽出):文単位で分割し、文間の語彙重複に基づく類似度行列を構築してTextRankスコアを算出
環境準備とデータ読み込み
必要なライブラリをインストール・読み込みます。
library(pacman)
p_load(tidyverse, tidytext, textrank, rio, jiebaR)
サンプルデータ(IT企業の求人記述4102件)を読み込みます。
import("hire_text.rda") -> job_descriptions
glimpse(job_descriptions)
名詞限定の分語処理
キーワード抽出の精度向上のため、品詞タグ付き分語を実施。名詞("n")のみを対象とします。
job_descriptions %>%
mutate(doc_id = row_number()) -> docs_with_id
# 品詞解析可能なワーカーを初期化
pos_tagger <- worker(type = "tag")
docs_with_id %>%
mutate(tokens_with_pos = map(hire_text, tagging, jieba = pos_tagger)) %>%
mutate(pos_df = map(tokens_with_pos, ~enframe(.x, name = "pos", value = "term"))) %>%
select(doc_id, pos_df) -> pos_annotated
TextRankによるキーワード抽出
各ドキュメントごとに名詞のみを抽出し、2-gramまでの共起パターンを考慮してTextRankを適用します。
extract_noun_keywords <- function(pos_df) {
# 名詞のみフィルタ + ngram生成(1〜2語)
noun_terms <- pos_df %>%
filter(str_starts(pos, "n")) %>%
pull(term)
textrank_keywords(
terms = noun_terms,
relevant = TRUE,
ngram_max = 2
)$keywords
}
pos_annotated %>%
mutate(keywords = map(pos_df, extract_noun_keywords)) %>%
select(-pos_df) -> keyword_results
各文書から上位3キーワードを抽出。優先順位は「出現回数>1」かつ「n-gram長が大きいほど高ランク」とします。
keyword_results %>%
unnest(keywords) %>%
filter(freq > 1) %>%
group_by(doc_id) %>%
slice_max(ngram, n = 3) %>%
ungroup() -> top3_per_doc
# 全体で最も頻出するキーワードTOP10
top3_per_doc %>%
count(keyword, sort = TRUE) %>%
head(10)
文単位の要約生成
文の区切りには空白文字列(全角・半角スペース、タブ、改行など)を用い、[:space:]+で分割します。
split_into_sentences <- function(text) {
str_split(text, "[:space:]+")[[1]] |>
enframe(name = NULL, value = "sentence") |>
mutate(sent_id = row_number())
}
segment_sentences <- function(text) {
sentences <- split_into_sentences(text)
word_worker <- worker()
sentences %>%
mutate(words = map(sentence, segment, jieba = word_worker)) %>%
select(-sentence) %>%
unnest(words) %>%
distinct(sent_id, words)
}
TextRankを用いた要約生成関数を定義します。単一文のみのドキュメントはスキップします。
summarize_document <- function(text) {
sents <- split_into_sentences(text)
if (nrow(sents) == 1) return(NA_character_)
terms <- segment_sentences(text)
textrank_sentences(data = sents, terminology = terms) %>%
summary(n = 1)
}
# テスト用に10〜20件を処理(実際の運用時は全体を対象)
job_descriptions %>%
slice(10:20) %>%
mutate(
doc_id = row_number(),
sentence_count = str_count(hire_text, "[:space:]+") + 1
) %>%
filter(sentence_count > 1) %>%
mutate(summary = map_chr(hire_text, summarize_document)) %>%
select(doc_id, sentence_count, summary)
TextRankはグラフ構造を前提とするため、大規模文書群への適用には計算コストが課題となります。しかし、中規模(数百〜数千文書)の業務用テキスト分析では、解釈性・再現性・非依存性(学習不要)という点で、TF-IDFやBERTベースのアプローチと比較しても十分な実用価値を持ちます。