単語単体の頻度カウント(Uni-gram)だけでは、「誤解 + を + 招く」のような特定の言葉の連続(パターン)や構文の特徴を捉えることはできません。
本記事では、SudachiPyで形態素解析を行った後、N-gram(バイグラム/トライグラム)を用いて連続する単語の組み合わせを抽出・集計し、特定の文体や構文パターンを自動抽出するPython実装を解説します。
1. N-gram(エヌグラム)解析とは?
N-gramとは、テキストをN個の単語(または文字)の連なりとして区切る手法です。
- Uni-gram(1-gram): ["誤解", "を", "招く"]
- Bi-gram(2-gram): [("誤解", "を"), ("を", "招く")]
- Tri-gram(3-gram): [("誤解", "を", "招く")]
単語単体ではなく Bi-gram(2連語) や Tri-gram(3連語) を解析することで、文章の「言い回し」「決まり文句」「定型フレーズ」を数値データとして捉えられるようになります。
2. 環境構築
必要なパッケージをインストールします。
pip install sudachipy sudachidict_core
3. 実装:SudachiPy × 形態素N-gram抽出パイプライン
テキストから形態素の原形(normalized_form)を取り出し、指定したN(2や3)の単語ペアを抽出する汎用関数を実装します。助詞や記号も含めた「構文パターン」を正確に抽出できます。
from collections import Counter
from sudachipy import dictionary, tokenizer
# 辞書とTokenizerのセットアップ
tokenizer_obj = dictionary.Dictionary().create()
def generate_morpheme_ngrams(
text: str, n: int = 2, mode=tokenizer.Tokenizer.SplitMode.A
) -> list[tuple[str, ...]]:
"""テキストを形態素解析し、指定されたサイズのN-gram(単語原形のタプル)リストを返します。
:param text: 解析対象テキスト
:param n: N-gramのサイズ (2: Bi-gram, 3: Tri-gram)
:param mode: 分割モード (デフォルト: Mode A)
:return: N-gramのタプルリスト
"""
tokens = tokenizer_obj.tokenize(text, mode)
# 補助記号や改行などのノイズを除去した形態素リストを作成
cleaned_tokens = [
t.normalized_form()
for t in tokens
if t.part_of_speech()[0] not in ["補助記号", "空白"]
]
# N-gramの生成
ngrams = [
tuple(cleaned_tokens[i : i + n])
for i in range(len(cleaned_tokens) - n + 1)
]
return ngrams
4. 実行例:文章パターンの比較解析
2つの異なる文体のテキスト(謝罪文パターンA・B)から Tri-gram(3連語) を抽出し、出現するフレーズ構造の違いを分析します。
# パターンA:言い訳・定型文が多いテキスト
text_a = """
今回の件につきましては、誤解を招く表現があり、大変不快な思いをさせてしまい誠に申し訳ございませんでした。
今後は誤解を招かないよう、真摯に対応を検討してまいります。
"""
# パターンB:具体的行動を示すテキスト
text_b = """
今回の不祥事につきまして、本日付けでサービスを一時停止いたします。
原因を調査し、再発防止策を策定した上で、来週月曜日に詳細を発表いたします。
"""
# Tri-gram (3-gram) の抽出とカウント
ngrams_a = generate_morpheme_ngrams(text_a, n=3)
ngrams_b = generate_morpheme_ngrams(text_b, n=3)
print("=== パターンA(定型・受動フレーズ)のTOP 3 ===")
for ngram, count in Counter(ngrams_a).most_common(3):
print(f"{' -> '.join(ngram)} : {count}回")
print("\n=== パターンB(具体行動フレーズ)のTOP 3 ===")
for ngram, count in Counter(ngrams_b).most_common(3):
print(f"{' -> '.join(ngram)} : {count}回")
実行結果
=== パターンA(定型・受動フレーズ)のTOP 3 ===
誤解 -> を -> 招く : 2回
を -> 招く -> 表現 : 1回
招く -> 表現 -> が : 1回
=== パターンB(具体行動フレーズ)のTOP 3 ===
サービス -> を -> 一時停止 : 1回
原因 -> を -> 調査 : 1回
再発防止策 -> を -> 策定 : 1回
「誤解 -> を -> 招く」 のような連続した動詞構文パターンが、明確なデータ(出現回数)として捕捉できていることが分かります。
5. 応用:特定品詞ペアのフィルタリング(名詞+動詞など)
N-gramに加えて「品詞フィルタリング」を組み合わせることで、「どんな名詞に対してどんな動詞が使われているか」という「目的語 + 動作」の構文ペアだけを取り出すことも可能です。
def extract_noun_verb_pairs(
text: str, mode=tokenizer.Tokenizer.SplitMode.A
) -> list[str]:
"""「名詞 + 助詞(を/に/へ) + 動詞」のパターンを抽出します。"""
tokens = tokenizer_obj.tokenize(text, mode)
pairs = []
for i in range(len(tokens) - 2):
t1, t2, t3 = tokens[i], tokens[i + 1], tokens[i + 2]
pos1 = t1.part_of_speech()[0]
pos2 = t2.part_of_speech()[0]
pos3 = t3.part_of_speech()[0]
# 「名詞 + 助詞('を') + 動詞」の構造を判定
if pos1 == "名詞" and pos2 == "助詞" and pos3 == "動詞":
if t2.surface() in ["を", "に", "へ"]:
pair_str = f"{t1.normalized_form()}{t2.surface()}{t3.normalized_form()}"
pairs.append(pair_str)
return pairs
# 実行
pairs = extract_noun_verb_pairs(text_b)
print("=== 抽出された「名詞+助詞+動詞」構造 ===")
print(pairs)
# 出力例: ['サービスを一時停止する', '原因を調査する', '再発防止策を策定する', '詳細を発表する']
6. まとめ
- 単語単体のカウントでは捉えきれない「言い回し」や「構文」の特徴は、SudachiPy × N-gram解析で効果的に捉えられる。
- Bi-gram / Tri-gram を使うことで、文章内の定型表現やコロケーション(連語)の抽出が可能。
- 品詞情報(Part-of-Speech)を掛け合わせることで、「名詞+動詞」のような特定文脈の構文だけをピンポイントでデータ化できる。