テキストデータ抽出テクニック

正規表現とNLPライブラリを活用したテキスト意図抽出の実践

Tags: 自然言語処理, Python, テキストマイニング, SpaCy, 正規表現

はじめに

日々の業務において、私たちは多種多様なテキストデータに触れています。顧客からの問い合わせメール、サポートセンターへのチャット記録、社内システムのログ、SNS上のユーザーの声など、これらのテキストデータにはビジネス上の重要な情報、特にユーザーやシステムが「何をしようとしているのか」「何を求めているのか」といった意図が埋もれています。

これらの意図を効率的に抽出し、分類・集計することで、顧客サポートの自動化、製品改善ニーズの把握、システム障害の予兆検知など、様々なタスクを高度化することが可能になります。しかし、これらのテキストは必ずしも構造化されておらず、手動での分析には限界があります。

本記事では、Webエンジニアの皆様が慣れ親しんでいる正規表現の強力さと、比較的容易に導入できる自然言語処理(NLP)ライブラリの機能を組み合わせることで、非構造化テキストから特定の意図を抽出する実践的な手法について解説します。特に、高度な機械学習モデルの構築に踏み込む前のステップとして、ルールベースやパターンマッチングを中心に、具体的なコード例とともにその考え方や実装方法をご紹介いたします。

テキスト意図抽出の基本的な考え方

テキストから意図を抽出するアプローチは、大きく分けて以下の2つがあります。

  1. ルールベース/パターンマッチング: 特定のキーワード、フレーズ、あるいは正規表現などのパターン定義に基づいて意図を識別する方法です。人間の専門知識をルールとして組み込むため、定義したパターンに対しては高い精度を発揮しやすい一方で、網羅的なパターン定義が難しい場合や、表現のゆらぎに弱いという側面があります。
  2. 統計的手法/機械学習: 大量の教師データ(テキストとそれに紐づく意図のラベル)を用いてモデルを学習させ、未知のテキストの意図を予測する方法です。表現のゆらぎに強く、大量データから複雑なパターンを自動的に学習できる可能性があります。一方で、質の高い教師データの準備が必要であり、学習済モデルの解釈が難しい場合があります。

実務においては、これらのアプローチを組み合わせたハイブリッド型が有効な場合が多くあります。特に、特定の重要なキーワードや構造に注目する必要がある場合はルールベースが適しており、そこからさらに分類を進めたり、表現のゆらぎに対応したりする際に統計的手法を検討するのが現実的です。

本記事では、まず導入として取り組みやすい、正規表現とNLPライブラリを組み合わせたルールベース/パターンマッチングによる意図抽出に焦点を当てます。

パターンマッチングによる意図抽出

最も基本的な意図抽出のアプローチは、テキスト中に特定のキーワードやフレーズが含まれているかを判定することです。Pythonにおいては、標準ライブラリであるreモジュールを用いることで、柔軟なパターン定義が可能になります。

例えば、「サービスの解約」に関する問い合わせを検出したい場合、以下のようなキーワードやフレーズが考えられます。

これらのキーワードを単純にOR条件で検索する方法に加え、正規表現を用いることで、より複雑なパターンに対応できます。

正規表現による基本的なパターンマッチング

import re

def extract_intent_with_regex(text):
    text_lower = text.lower() # 大文字・小文字を区別しない場合

    # 解約意図に関連しそうなパターン
    cancel_patterns = [
        r"解約",
        r"退会",
        r"やめたい",
        r"サービス.*停止", # "サービス"と"停止"の間に任意の文字が0回以上
        r"契約.*終了",   # "契約"と"終了"の間に任意の文字が0回以上
    ]

    for pattern in cancel_patterns:
        if re.search(pattern, text_lower):
            return "解約/退会"

    # 料金に関する問い合わせ意図に関連しそうなパターン
    billing_patterns = [
        r"料金",
        r"費用",
        r"いくら",
        r"請求",
        r"支払い",
    ]

    for pattern in billing_patterns:
        if re.search(pattern, text_lower):
            return "料金に関する問い合わせ"

    # その他の意図(ここではデフォルト)
    return "その他"

# サンプルテキスト
texts = [
    "サービスの解約方法を教えてください。",
    "今月の請求金額について確認したいです。",
    "アカウントを退会したいのですが、手続きは必要ですか?",
    "機能について質問があります。",
    "契約を終了したいです。",
    "サービスの利用を停止したいのですが。",
]

for text in texts:
    intent = extract_intent_with_regex(text)
    print(f"テキスト: '{text}' -> 意図: {intent}")

このコードでは、特定の意図に関連する正規表現パターンをリストとして定義し、テキストがそれらのパターンのいずれかにマッチするかを順に判定しています。正規表現を使うことで、「サービスの利用を停止したいのですが」のように、単語が離れていても関連するフレーズを捉えることができます。

しかし、正規表現だけでは表現のゆらぎや同義語、文脈による意味の違いを捉えるのが難しい場合があります。例えば、「解約したい」と「解約は必要ですか?」では意味合いが大きく異なります。また、「サービス停止」が文字通りサービス提供の一時停止を意味する場合もあります。

NLPライブラリを活用した意図抽出の補強

NLPライブラリ(例: SpaCy, NLTK)を利用することで、テキストの構造(単語、品詞、文の依存関係など)を解析し、より洗練されたパターンマッチングやルール定義が可能になります。ここでは、扱いやすさと高性能で知られるSpaCyを例にご紹介します。

SpaCyの導入と基本的な使い方

SpaCyを利用するには、まずインストールと日本語モデルのダウンロードが必要です。

pip install spacy
python -m spacy download ja_core_news_sm # または ja_core_news_lg など

インストール後、以下のようにモデルをロードしてテキストを処理できます。

import spacy

# 日本語モデルのロード
# 初回ロード時は時間がかかる場合があります
nlp = spacy.load("ja_core_news_sm")

text = "サービスの解約方法を教えてください。"
doc = nlp(text)

# 処理されたテキスト(Docオブジェクト)から各単語(Token)の情報にアクセス
for token in doc:
    print(f"テキスト: {token.text}, 品詞: {token.pos_}, 形態素解析タグ: {token.tag_}, 係り受け: {token.dep_}, 親単語: {token.head.text}")

SpaCyでテキストを処理すると、Docオブジェクトが生成され、その中に含まれる各単語(Token)に対して、形態素解析結果(品詞、原型など)、固有表現(人名、地名など)、係り受け関係などの情報が付与されます。これらの情報を活用することで、単なるキーワード一致以上の複雑なルールを定義できます。

品詞タグと依存構造解析を活用したパターン定義

「解約したい」のような「[特定の単語] + [特定の品詞の単語]」や「[特定の単語] に係っている [特定の品詞の単語]」といったパターンは、品詞タグ(token.pos_)や依存関係(token.dep_)を利用して抽出できます。

例えば、「〜したい」という意図は、「したい」(助動詞または動詞)が特定の動詞や名詞に係っているパターンとして捉えられます。また、「解約」という名詞に「たい」という助動詞が係っている文は、強い解約意図を示唆している可能性が高いです。

import spacy

nlp = spacy.load("ja_core_news_sm")

def extract_intent_with_spacy(text):
    doc = nlp(text)

    # 解約意図のパターン例:
    # 1. 「解約」「退会」などのキーワードが含まれる
    # 2. かつ、「たい」「ほしい」「依頼」「要求」などの希望・要求を示す表現が近くにある(ここでは係り受け関係で判定)
    # 3. または、「停止」「終了」などの単語が特定の文脈で使用されている

    cancel_keywords = {"解約", "退会", "停止", "終了"}
    desire_request_tags = {"たい", "ほしい", "ください"} # 形態素解析タグや原型で判定することも可能

    for token in doc:
        # 例:名詞「解約」があり、それに「たい」などが係っているか、あるいはその逆
        if token.text in cancel_keywords:
            # 自分に係っている単語をチェック
            for child in token.children:
                if child.lemma_ in desire_request_tags or child.text in desire_request_tags: # 原型やテキストで判定
                    return "解約/退会意図(強い)"
            # 自分が係っている親単語をチェック
            if token.dep_ != "ROOT": # ROOTトークンでなければ
                if token.head.lemma_ in desire_request_tags or token.head.text in desire_request_tags:
                     return "解約/退会意図(強い)"

        # 例:「料金」という名詞が含まれる
        if token.text == "料金" and token.pos_ == "NOUN":
             return "料金に関する問い合わせ"

        # より汎用的なパターン定義には SpaCyのMatcher が便利です
        # ここでは簡易的なチェックに留めます

    return "その他"

# サンプルテキスト
texts = [
    "サービスの解約をしたいのですが。", # 「解約」+「したい」
    "料金について教えてください。",       # 「料金」+依頼表現
    "退会したいです。",                 # 「退会」+「したい」
    "このサービスの利用を停止したいです。", # 「停止」+「したい」
    "今月の請求額を確認できますか?",    # 「請求」を含むが、依頼表現
    "解約手続きはどのように行いますか?", # 「解約」を含むが、方法を質問
]

print("\n--- SpaCyを使った意図抽出 ---")
for text in texts:
    intent = extract_intent_with_spacy(text)
    print(f"テキスト: '{text}' -> 意図: {intent}")

上記の例では、単語のテキストだけでなく、品詞(token.pos_)や依存関係(token.dep_, token.head, token.children)を利用して、単語間の構造的な関係に基づいた判定を行っています。これにより、「解約」という単語が含まれるだけの文と、「解約したい」のように強い意図を示す文とを区別することが可能になります。

SpaCyには、より複雑なトークンパターンや依存関係パターンを効率的に定義・適用できるMatcherDependencyMatcherといった機能も提供されています。これらを活用することで、大規模なルールベースシステムを構築することも可能です。

簡易的な分類手法との組み合わせ

複数のパターンやルールを組み合わせることで、より精度高く意図を分類することができます。これは、厳密にはパターンマッチングの拡張であり、簡易的なルールベース分類器とみなせます。

例えば、以下のようなルールセットを定義し、テキストを当てはめていくことで意図を判定できます。

  1. 強い解約意図パターン(例:「解約したい」「退会希望」など)にマッチするか? → マッチすれば「解約」
  2. 料金関連パターン(例:「料金」「請求」「支払い」など)にマッチするか? → マッチすれば「料金問い合わせ」
  3. 機能に関する質問パターン(例:「使い方」「機能」「〜できますか」など)にマッチするか? → マッチすれば「機能質問」
  4. 上記いずれにもマッチしない場合 → 「その他」

判定ルールの順番も重要になる場合があります(特定の意図を優先して判定するなど)。

import spacy
import re

nlp = spacy.load("ja_core_news_sm", disable=["ner"]) # 固有表現認識は不要なら無効化して軽量化

def classify_intent_rules(text):
    doc = nlp(text)
    text_lower = text.lower()

    # --- ルールセット ---

    # ルール 1: 強い解約/退会意図
    # キーワードと、それに付随する強い要求表現の組み合わせ
    cancel_keywords = {"解約", "退会"}
    desire_request_tags = {"たい", "ほしい", "ください", "希望"} # 原型やテキストで定義

    found_cancel_keyword = False
    found_desire_request = False

    for token in doc:
        if token.text in cancel_keywords:
            found_cancel_keyword = True
        # 係り受け関係で要求表現を探す (簡易的に全てのトークンをチェック)
        if token.lemma_ in desire_request_tags or token.text in desire_request_tags:
             found_desire_request = True

    if found_cancel_keyword and found_desire_request:
         # 正規表現でも補強
         if re.search(r"(解約|退会).*?(たい|ほしい|希望|ください)", text_lower):
             return "解約/退会(強い意図)"

    # ルール 2: 料金に関する問い合わせ
    if re.search(r"(料金|費用|いくら|請求|支払い)", text_lower):
        return "料金に関する問い合わせ"

    # ルール 3: 機能に関する質問 (簡易版)
    if re.search(r"(使い方|機能|できますか|どうすれば)", text_lower) and any(token.pos_ in {"VERB", "NOUN", "ADJ"} for token in doc): # 動詞、名詞、形容詞などが含まれる文であること
         return "機能に関する質問"

    # どのルールにもマッチしない場合
    return "その他"

# サンプルテキスト
texts = [
    "サービスの解約をしたいのですが、手続きを教えてください。",
    "退会希望です。",
    "今月の利用料金はいくらになりますか?",
    "新しい機能の使い方を教えてほしい。",
    "アカウント情報を変更したいのですが、どうすればよいですか?",
    "システム障害の可能性はありますか?", # その他
    "料金プランについて質問です。", # 料金に関する問い合わせ
]

print("\n--- ルールベース分類による意図抽出 ---")
for text in texts:
    intent = classify_intent_rules(text)
    print(f"テキスト: '{text}' -> 意図: {intent}")

この例では、複数の条件(キーワード、品詞、依存関係、正規表現)を組み合わせて、より特定の意図を抽出するルールを定義しています。実際のシステムでは、これらのルールをYAMLやJSONなどの設定ファイルとして管理し、メンテナンス性を高めることが一般的です。

実務への応用事例と考慮事項

本記事で紹介したようなパターンマッチングや簡易的なルールベース分類による意図抽出は、様々な実務タスクに応用できます。

これらの応用において、以下の点を考慮する必要があります。

パターンのメンテナンス性

ルールベースのアプローチは、新しい表現や意図が出現した場合にルールの追加・修正が必要です。ルールが複雑化するとメンテナンスが困難になるため、ルールを構造化して管理したり、テストケースを用意したりといった工夫が重要になります。

曖昧性の解消

同じ表現でも文脈によって意味が異なる場合があります。例えば、「確認したい」が「状態を確認したい」なのか「承認を確認したい」なのか、などです。より高度な文脈解析が必要な場合は、シンプルなパターンマッチングでは限界がある可能性があります。SpaCyの固有表現認識機能で対象エンティティ(製品名、日付など)を抽出し、それと意図を結びつけることで、曖昧性を減らせる場合があります。

パフォーマンス

大量のテキストデータを処理する場合、正規表現やNLPライブラリによる解析には一定の処理コストがかかります。特にSpaCyは高機能である反面、モデルのロードやテキスト解析に時間がかかる場合があります。もし速度がボトルネックとなる場合は、処理対象の絞り込み、NLPパイプラインの不要なコンポーネント無効化(例: disable=["ner", "parser"])、あるいはより高速な代替手段の検討が必要になります。

精度評価

構築したルールやパターンの精度を測るには、少量のサンプルデータに対して手動で意図をラベリングし、自動抽出結果と比較するなどの評価が必要です。どのような意図をどの程度正確に抽出できているのかを把握することで、ルールの改善点が見えてきます。

システムへの組み込み

これらの処理をWebサービスやバッチ処理としてシステムに組み込む場合、NLPライブラリのモデルロードをアプリケーション起動時に一度だけ行う、プロセスプールを利用して並列処理を行う、マイクロサービスとして独立させる、といった設計上の考慮が必要です。特にSpaCyはモデルサイズが大きいため、デプロイ環境のリソース(メモリ、ディスク容量)に注意が必要です。

まとめ

本記事では、Webエンジニアの皆様が既存のスキルセットからアプローチしやすい、正規表現とPythonのNLPライブラリ(SpaCy)を組み合わせたテキスト意図抽出の基本的な考え方と実践的な手法をご紹介しました。

単純なキーワードや正規表現によるパターンマッチングから始まり、SpaCyを活用して品詞や依存関係といった言語構造を捉えることで、より精度高く、表現のゆらぎにもある程度対応できるルールベースの抽出器を構築できることを示しました。

ルールベースのアプローチは、データ量が少なくても開始できる、結果の解釈が容易である、特定の重要パターンを確実に捉えられる、といった利点があります。一方で、網羅性や表現の多様性への対応には限界があり、ルールのメンテナンスが課題となる場合があります。

まずは身近なテキストデータ(例えば、自身のチームで受け付けている問い合わせ内容の一部など)に対して、本記事で紹介したような正規表現やSpaCyを使ったパターン定義を試してみていただければ幸いです。そこからさらに高度な意図抽出を目指す場合は、少量の手動ラベリングを行い、分類モデルの導入などを検討していく流れが一般的です。

非構造化テキストからの情報抽出は奥深いテーマですが、一歩ずつ進めることで、業務効率化や新たな知見の獲得に繋がっていくでしょう。