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

Pythonと自然言語処理による業務テキストからの変化・状態遷移抽出の実践テクニック

Tags: Python, 自然言語処理, テキスト抽出, SpaCy, 業務自動化

はじめに

日々の業務で扱うテキストデータ、例えば顧客対応履歴、システムログ、報告書などには、対象の状態や状況が時間とともにどのように変化したかを示す記述がしばしば含まれています。例えば、「顧客ステータスが保留から対応中に変わった」「サーバの負荷が急激に増加した」「プロジェクトの状態が計画遅延になった」といった情報です。これらの変化や状態遷移を自動的に捉えることは、状況の迅速な把握、プロセスの自動化、予兆検知などに不可欠です。

しかし、自然言語で記述される変化や遷移の表現は多様であり、単一のキーワードや単純な正規表現だけでは網羅的に、かつ正確に抽出することは困難です。本記事では、Pythonと自然言語処理(NLP)ライブラリを組み合わせることで、業務テキストから変化や状態遷移に関する記述を効果的に抽出する具体的な手法について解説します。

変化・状態遷移を表す記述のパターン

テキスト中で変化や状態遷移を示す表現には様々な形があります。典型的なパターンをいくつか挙げてみます。

これらのパターンは単独で現れることもあれば、組み合わさってより複雑な構造を作ることもあります。「[対象]の状態が[変化前]から[変化後]に変わった」のように、変化の主体、変化前後の状態、そして変化を示す動詞や助詞がセットで記述されることが多いです。

抽出手法のアプローチ

変化・状態遷移の抽出には、テキストの表面的なパターンだけでなく、単語の品詞や文の構造を理解することが有効です。ここでは、以下の複数のアプローチを組み合わせることを考えます。

  1. キーワードと正規表現による初期フィルタリング: ある程度定型的な表現や、特定のキーワードを含む文を抽出するための第一段階として活用します。シンプルですが、網羅性や精度に限界があります。
  2. 品詞タグ付けと固有表現抽出: 動詞、名詞、助詞といった品詞情報を利用し、変化を示す可能性のある単語や、変化の主体・対象となりうる名詞句を特定します。また、SpaCyなどのライブラリによる固有表現抽出が、特定の状態名(例: 製品名、エラーコードなど)の識別に役立つ場合があります。
  3. 依存構造解析の活用: 文中の単語間の文法的な関係(誰が何をしたか、何が何にかわったかなど)を解析します。これにより、「〜から」や「〜へ/に」といった助詞がどの名詞句にかかっているか、変化を示す動詞の主語は何か、などを構造的に捉えることが可能になり、より正確な変化前後の状態を抽出できます。

これらのアプローチを組み合わせることで、多様な表現に対応しつつ、変化の主体、変化前後の状態、変化の性質(開始、終了、増減など)といった詳細情報を抽出することを目指します。

実装例:PythonとSpaCyを使った抽出

PythonとNLPライブラリであるSpaCyを用いた実装例を示します。SpaCyは、高速なトークン化、品詞タグ付け、依存構造解析、固有表現抽出などの機能を提供しており、比較的容易に利用できます。

準備

まず、SpaCyをインストールし、使用する言語モデルをダウンロードします。日本語を扱う場合はja_core_news_smなどのモデルを利用します。

pip install spacy
python -m spacy download ja_core_news_sm

サンプルデータ

顧客対応履歴の一部を想定したサンプルテキストを準備します。

texts = [
    "お客様のステータスを「初期連絡待ち」から「対応中」に変更しました。",
    "エラーコード E101 が発生し、システムが警告状態になりました。",
    "サーバのリソース使用率が急激に増加しています。",
    "プロジェクトのフェーズ1が完了しました。",
    "担当者がA氏からB氏に変わりました。",
    "特に状態変化は見られませんでした。" # 変化を含まない例
]

手法1: シンプルなキーワードと正規表現

まずは単純な正規表現で「〜から〜に変わった/変更した」のようなパターンを抽出してみます。

import re

def extract_simple_change(text):
    # 例: 「「〜」から「〜」に変更/変わ」のパターン
    # 注意: これは非常に限定的なパターンにしかマッチしません
    pattern = r'「(.+?)」から「(.+?)」に(変更|変わ)[しましたた]'
    match = re.search(pattern, text)
    if match:
        before = match.group(1)
        after = match.group(2)
        action = match.group(3)
        return {
            "type": "state_change",
            "before": before,
            "after": after,
            "action": action,
            "text": text
        }
    # 例: 「〜が完了しました」のようなパターン
    pattern_completed = r'(.+?)が完了しました'
    match_completed = re.search(pattern_completed, text)
    if match_completed:
        subject = match_completed.group(1)
        return {
            "type": "completion",
            "subject": subject,
            "text": text
        }
    return None

print("--- シンプルなキーワード・正規表現による抽出 ---")
for text in texts:
    result = extract_simple_change(text)
    if result:
        print(result)

この方法では、定義した正規表現パターンに厳密に一致するものしか抽出できません。表現の揺れや複雑な構造には対応できません。

手法2: SpaCyを使った依存構造解析の活用

より柔軟に変化・状態遷移を捉えるために、SpaCyによる依存構造解析を利用します。

import spacy

# 日本語モデルをロード
# 最初にダウンロードが必要です: python -m spacy download ja_core_news_sm
try:
    nlp = spacy.load("ja_core_news_sm")
except OSError:
    print("SpaCyモデル 'ja_core_news_sm' が見つかりません。実行してください: python -m spacy download ja_core_news_sm")
    exit()

def extract_change_with_spacy(text):
    doc = nlp(text)
    results = []

    # 変化を示す可能性のある動詞のリスト (適宜拡張が必要)
    change_verbs = ["変更", "変わ", "移行", "なる", "増加", "減少", "完了", "開始"]

    for token in doc:
        # 変化を示す動詞に注目
        if token.lemma_ in change_verbs and token.pos_ == "VERB":
            change_info = {"verb": token.lemma_, "text": text}

            # 依存構造を辿って変化前・変化後の状態や対象を特定
            # 例: 「から」「へ」「に」の助詞が修飾する名詞句を探す
            before_state = None
            after_state = None
            subject = None

            for child in token.children:
                # 「〜から」の関係を探す
                if child.dep_ == "advcl" and child.text.endswith("から"): # 複合的な助詞表現の可能性
                    # 「から」句の主辞(通常は助詞自身)の子や親をたどって名詞句を探す
                    # 簡単な例として、「から」の直前の名詞句を探す
                    if child.i > 0 and doc[child.i-1].pos_ == "NOUN":
                         before_state = doc[child.i-1].text
                    # より堅牢には、childのサブツリーを探索する必要がある
                elif child.dep_ == "obl": # 補語 (〜に、〜へ など)
                    # oblの子をたどって名詞句を探す
                    for grandchild in child.children:
                        if grandchild.dep_ in ("nmod", "compound", "obj", "nsubj"): # 名詞修飾、複合語、目的語、主語など
                             after_state = "".join([t.text for t in grandchild.subtree]).strip()
                             break # 最初の関連名詞句を採用
                    if not after_state: # 直接の補語が名詞句の場合
                         if child.pos_ == "NOUN":
                              after_state = child.text
                         elif child.pos_ == "PROPN":
                             after_state = child.text


                # 主語を探す
                elif child.dep_ == "nsubj":
                    subject = "".join([t.text for t in child.subtree]).strip()

            # 特定の動詞(例: 「になる」)に対する処理を追加
            if token.lemma_ == "なる":
                 # 「〜が〜になる」のパターン
                 # nsubj (主語) と obl (〜に) の関係を見る
                 subject_token = None
                 after_state_token = None
                 for child in token.children:
                     if child.dep_ == "nsubj":
                         subject_token = child
                     elif child.dep_ == "obl":
                         after_state_token = child

                 if subject_token and after_state_token:
                      subject = "".join([t.text for t in subject_token.subtree]).strip()
                      after_state = "".join([t.text for t in after_state_token.subtree]).strip()
                      change_info.update({"subject": subject, "after": after_state})
                      results.append(change_info)
                      continue # この動詞の処理は完了

            # 「〜から〜へ/に 変更/変わる」のようなパターン
            if token.lemma_ in ["変更", "変わ"]:
                 from_clause = None
                 to_clause = None
                 subject_token = None

                 for child in token.children:
                     if child.text.endswith("から"): # 例: 「初期連絡待ち」から
                          from_clause = "".join([t.text for t in child.subtree]).strip()
                     elif child.text.endswith("に") or child.text.endswith("へ"): # 例: 「対応中」に
                          to_clause = "".join([t.text for t in child.subtree]).strip()
                     elif child.dep_ == "nsubj": # 主語
                          subject_token = "".join([t.text for t in child.subtree]).strip()

                 if from_clause and to_clause:
                      change_info.update({"subject": subject_token, "before": from_clause, "after": to_clause})
                      results.append(change_info)
                      continue # この動詞の処理は完了


            # その他の変化動詞(増加、減少、完了、開始など)
            if token.lemma_ in ["増加", "減少", "完了", "開始"]:
                 if subject:
                      change_info.update({"subject": subject})
                      results.append(change_info)
                      continue


    return results

print("\n--- SpaCyによる依存構造解析を使った抽出 ---")
for text in texts:
    results = extract_change_with_spacy(text)
    if results:
        for result in results:
             print(result)
    else:
        print(f"No change detected in: {text}")

注: 上記のSpaCyコード例は、特定の依存関係パターンに合わせた比較的シンプルな実装です。実際の多様な表現に対応するためには、より多くの依存関係パターンや品詞の組み合わせを考慮し、探索ロジックを洗練させる必要があります。特に「〜から」や「〜に」といった助詞句が修飾する名詞句を正確に特定するには、subtreeancestorschildrenなどを組み合わせて構造をより深く探索する必要があります。

実務への応用と考慮事項

応用事例

考慮事項

まとめ

業務テキストからの変化・状態遷移の抽出は、テキストデータに隠された動的な情報を引き出す強力な手段です。単なるキーワードマッチングや正規表現だけでは難しい多様な表現に対応するためには、品詞タグ付けや依存構造解析といったNLPの技術が有効です。

本記事で示したPythonとSpaCyを使ったコード例は、その基本的なアプローチを示すものです。実際の業務においては、対象となるテキストの特性に合わせて、より詳細なルール設計、辞書の拡充、そして継続的な精度評価と改善が必要になります。本記事が、皆様の業務におけるテキスト情報抽出の課題解決の一助となれば幸いです。