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

PythonとNLPによる業務テキストからの手順・ステップ情報抽出テクニック

Tags: Python, 自然言語処理, テキスト抽出, 手順, ステップ, SpaCy, 依存構造解析

はじめに:手順・ステップ情報抽出の重要性と課題

日々の業務において、私たちは多様なテキストデータを取り扱います。例えば、製品のマニュアル、システムの操作ログ、顧客からの問い合わせ履歴、バグ報告書などです。これらのテキストには、特定のタスクを実行するための「手順」や、一連の処理の「ステップ」に関する重要な情報が含まれていることが少なくあります。

これらの手順やステップ情報を効率的に抽出できれば、以下のような様々な応用が可能になります。

しかし、これらの情報は必ずしも構造化された形式で記述されているわけではありません。番号付きリスト、箇条書き、あるいは単なる自然文として表現されることが一般的です。単純な正規表現だけでは、表現の揺れや複雑な構造に対応しきれない場面が多々発生します。

本記事では、Pythonと自然言語処理(NLP)の技術を活用し、このような非構造化・半構造化テキストから手順やステップ情報を効率的かつ高精度に抽出するための具体的なテクニックをご紹介します。特に、NLPライブラリであるSpaCyを用いた依存構造解析を中心に解説し、実務で役立つコード例を豊富に提供いたします。

手順・ステップ情報のテキストパターン

テキスト中の手順やステップ情報は、様々なパターンで表現されます。これらのパターンを理解することが、適切な抽出手法を選択する第一歩となります。

  1. リスト形式:
    • 番号付きリスト: 1. まずAを行います。2. 次にBを実行します。
    • 箇条書きリスト: - Cを設定します。* Dを確認します。
  2. 指示表現を含む自然文:
    • 命令形: 「システムにログインしてください。」
    • 動詞+目的語の構造: 「設定ファイルを編集する。」 「ボタンをクリックする。」
    • 特定の助詞・助動詞を伴う表現: 「〜を行う必要があります。」 「〜の手順は以下の通りです。」
  3. 時系列を示す接続詞・副詞:
    • まず, 次に, その後, 最後に など。
  4. 特定のキーワード:
    • 手順, ステップ, 操作方法, 以下を実施してください など。

これらのパターンは単独で現れるだけでなく、組み合わさって使用されることもあります。例えば、「1. まず、コントロールパネルを開いてください。」のように、リスト形式と指示表現が同時に使われるケースです。

NLPによる手順・ステップ抽出の基本アプローチ

単純なキーワードや正規表現だけでは、同じ単語でも文脈によって意味が異なる場合や、表現の揺れに対応できません。ここでNLPが役立ちます。特に以下の要素を活用します。

これらの情報を組み合わせることで、「〜をする」という指示や操作を表現している部分を、その操作の対象も含めて構造的に抽出することが可能になります。

SpaCyを活用した手順・ステップ抽出の実践

ここでは、強力なオープンソースNLPライブラリであるSpaCyを用いて、具体的な手順・ステップ情報の抽出方法を見ていきます。SpaCyは高速で高精度なNLP機能を提供しており、実務での利用に適しています。

まず、SpaCyをインストールします。

pip install spacy
python -m spacy download ja_core_news_sm # 日本語モデルのダウンロード

基本的な指示表現の抽出

動詞とその目的語の組み合わせは、手順や操作を示す最も一般的なパターンの一つです。SpaCyの依存構造解析を利用して、このパターンを抽出します。

例えば、「設定ファイルを編集する」という文では、「編集する」が動詞、「設定ファイル」がその目的語です。依存構造解析の結果では、「設定ファイル」は「編集する」に対する obj (目的語) の関係を持ちます。

import spacy

# 日本語モデルをロード
nlp = spacy.load("ja_core_news_sm")

text = "以下の手順でシステムをアップデートしてください。まず、設定ファイルを開きます。次に、バージョン情報を確認し、必要な変更を加えます。最後に、システムを再起動します。"

# テキストを処理
doc = nlp(text)

print("--- 依存構造解析結果の例 ---")
for token in doc:
    # token.text: テキスト, token.lemma_: 原形, token.pos_: 品詞, token.dep_: 依存関係, token.head.text: 係り先のテキスト
    print(f"{token.text}\t{token.lemma_}\t{token.pos_}\t{token.dep_}\t{token.head.text}")
print("--------------------------")

print("\n--- 手順・ステップ候補の抽出 ---")
procedure_steps = []

for sent in doc.sents: # 文ごとに処理
    step_parts = []
    # 文内のトークンを走査
    for token in sent:
        # 動詞(VERB)を検出
        if token.pos_ == "VERB":
            action = token.lemma_ # 原形をアクションとする
            target = ""
            # その動詞にobj(目的語)として直接かかっている名詞句を探す
            for child in token.children:
                if child.dep_ == "obj" and child.pos_ == "NOUN":
                    target = child.text # 目的語のテキストを取得
                    # 目的語に付属する形容詞や名詞なども含める場合は、child.subtreeを探索するなど拡張が必要
                    break # シンプルな例として最初の目的語を採用

            if action:
                 # 必要に応じて、アクションと対象を結合したり、特定の条件でフィルタリング
                step_description = f"{action}"
                if target:
                    step_description += f" ({target})"

                # 副詞句などを前置することも検討
                # 例: まず (action)
                # 時系列を示す語を検出してステップに追加することも可能
                temporal_modifier = ""
                for ancestor in token.ancestors:
                     if ancestor.text in ["まず", "次に", "最後に"]:
                         temporal_modifier = ancestor.text
                         break

                if temporal_modifier:
                    step_description = f"{temporal_modifier}、{step_description}"

                step_parts.append(step_description)

    # 抽出されたパーツを結合してステップとしてリストに追加(シンプルな例)
    if step_parts:
         procedure_steps.append(" ".join(step_parts)) # 文全体をステップとして扱うなど、結合方法は要検討

# 結果の表示
for i, step in enumerate(procedure_steps):
    print(f"ステップ {i+1}: {step}")

実行結果の例:

--- 依存構造解析結果の例 ---
以下  以下  NOUN    nmod    手順
の   の   ADP case    以下
手順  手順  NOUN    nsubj   アップデート
で   で   ADP case    手順
システム    システム    NOUN    obj アップデート
を   を   ADP case    システム
アップデート  アップデート  NOUN    root    アップデート
し   する  VERB    aux アップデート
て   て   AUX aux アップデート
ください    くださる    VERB    acl アップデート
。   。   PUNCT   punct   アップデート
まず  まず  ADV advmod  開く
、   、   PUNCT   punct   開く
設定  設定  NOUN    compound    ファイル
ファイル    ファイル    NOUN    obj 開く
を   を   ADP case    ファイル
開きます    開く  VERB    root    開きます
。   。   PUNCT   punct   開きます
次に  次に  ADV advmod  確認
、   、   PUNCT   punct   確認
バージョン   バージョン   NOUN    compound    情報
情報  情報  NOUN    obj 確認
を   を   ADP case    情報
確認  確認  NOUN    root    確認
し   する  AUX aux 確認
、   、   CCONJ   cc  加える
必要  必要  ADJ root    必要
な   だ   AUX cop 必要
変更  変更  NOUN    obj 加える
を   を   ADP case    変更
加え  加える VERB    conj    確認
ます  ます  AUX aux 加え
。   。   PUNCT   punct   確認
最後に 最後に ADV advmod  再起動
、   、   PUNCT   punct   再起動
システム    システム    NOUN    obj 再起動
を   を   ADP case    システム
再起動 再起動 NOUN    root    再起動
し   する  AUX aux 再起動
ます  ます  AUX aux 再起動
。   。   PUNCT   punct   再起動
--------------------------

--- 手順・ステップ候補の抽出 ---
ステップ 1: アップデート (システム)
ステップ 2: まず、開く (ファイル)
ステップ 3: 次に、確認 (情報) 加える (変更)
ステップ 4: 最後に、再起動 (システム)

このコードでは、各文を処理し、動詞を見つけたらその目的語 (dep_ == "obj") を探しています。また、時系列を示す副詞(まず, 次に, 最後に)があれば、それも一緒に抽出しています。

リスト形式の手順抽出

番号付きリストや箇条書きは、正規表現と組み合わせることで比較的容易に抽出できます。抽出した各項目に対して、上記のようなNLP処理を適用することで、より詳細な操作内容を構造化できます。

import re
import spacy

nlp = spacy.load("ja_core_news_sm")

text_list = """
作業手順:
1. システムにログインします。
2. 設定画面を開いてください。
3. ユーザー情報を入力し、保存ボタンをクリックします。
4. ログアウトします。
"""

# リスト形式の項目を正規表現で抽出
list_items = re.findall(r'^\s*[\d\-\*]\s*(.+)', text_list, re.MULTILINE)

print("\n--- リスト項目ごとのNLP処理 ---")
processed_steps = []

for i, item in enumerate(list_items):
    doc_item = nlp(item)
    step_description = item # 元のテキストを保持
    actions = []

    for token in doc_item:
        if token.pos_ == "VERB":
            action = token.lemma_
            target = ""
            for child in token.children:
                # 目的語やそれに類する依存関係(例: obj, obl, iobjなど、モデルによる)を探す
                if child.dep_ in ["obj", "obl", "iobj"] and child.pos_ in ["NOUN", "PROPN"]:
                     target = child.text
                     # より広範な名詞句を取得する場合:
                     # target = " ".join([t.text for t in child.subtree])
                     break

            action_desc = action
            if target:
                action_desc += f" ({target})"
            actions.append(action_desc)

    # 抽出したアクション情報を整形してリストに追加
    if actions:
         processed_steps.append(f"ステップ {i+1}: {item} -> アクション: {', '.join(actions)}")
    else:
         processed_steps.append(f"ステップ {i+1}: {item} -> アクション候補なし")


# 結果の表示
for step in processed_steps:
    print(step)

実行結果の例:

--- リスト項目ごとのNLP処理 ---
ステップ 1: システムにログインします。 -> アクション: ログイン (システム)
ステップ 2: 設定画面を開いてください。 -> アクション: 開く (画面)
ステップ 3: ユーザー情報を入力し、保存ボタンをクリックします。 -> アクション: 入力 (情報), クリック (ボタン)
ステップ 4: ログアウトします。 -> アクション: ログアウト

この例では、正規表現でリスト項目を抽出し、それぞれの項目に対してNLP処理を行っています。抽出される依存関係 (dep_) は使用するSpaCyモデルによって若干異なる場合があるため、obj だけでなく obliobj など、目的語に相当する可能性のある関係も考慮すると、より網羅的になることがあります。

より複雑な表現への対応とカスタム化

実際の業務テキストでは、単純な「動詞 + 目的語」だけでなく、様々な修飾語やオプション情報が付随します。例えば、「必ず管理権限でシステムにログインします。」や「設定ファイルをエディタで編集する。」などです。

これらの情報を抽出するには、依存構造ツリーをより深く解析する必要があります。

SpaCyのtoken.childrentoken.ancestorstoken.subtree などのプロパティを活用することで、これらの関連情報を構造的に捉えることができます。特定の業務ドメインに特化した表現が多い場合は、カスタムの依存関係パターンを定義してマッチングを行う「ルールベースマッチング」や、固有表現認識(NER)の機能を活用して操作対象の種類(例: ファイル名、ユーザー名、ボタン名)を識別することも有効です。

応用事例

本記事で解説した手法は、以下のような実務課題に応用できます。

実務での考慮事項とパフォーマンス

本手法を実務システムに組み込む際には、いくつかの考慮事項があります。

まとめ

本記事では、PythonとSpaCyを活用した、業務テキストからの手順・ステップ情報抽出テクニックについて解説しました。単純な正規表現では対応が難しい自然文中の指示表現や、リスト形式の手順を、品詞タグ付けや依存構造解析を用いて構造的に捉える具体的な手法とコード例をご紹介しました。

これらのテクニックは、マニュアルの解析、ログからの処理シーケンス特定、問い合わせ内容からの手順抽出など、様々な実務課題に応用可能です。実際のテキストデータは多様な表現を含むため、本記事で紹介した基本アプローチをベースに、正規表現、カスタム辞書、より複雑な依存関係パターンの定義などを組み合わせることで、抽出精度や網羅率を向上させることができます。

自然言語処理は強力なツールですが、万能ではありません。処理対象となるテキストデータの特性をよく理解し、課題に対して最も効果的な手法を選択・組み合わせることが、実務で成果を出すための鍵となります。本記事が、皆様のテキストからの情報抽出の実践に役立てば幸いです。