PythonとNLPライブラリを活用した階層構造を持つテキストからの情報抽出
はじめに
業務で扱うテキストデータには、単なる連続した文章だけでなく、見出し、箇条書き、インデントなどによって構造化されたものが多く存在します。例えば、会議の議事録、技術仕様書、顧客からの要望リスト、製品レビューのまとめなどは、特定の情報が階層的な関係性を持って記述されていることが一般的です。
こうした階層構造を持つテキストから、「どの議題について、誰が担当し、何が決定されたか」や、「どの製品機能について、どのような良い点・悪い点が挙げられているか」といった情報を自動的に抽出することは、データ分析や業務プロセスの自動化において非常に有用です。
しかし、このような構造は正規表現だけですべてを捉えるのが難しい場合があります。特に、階層の深さが可変であったり、使用される区切り記号や表現に揺れがあったりする場合、複雑な正規表現は可読性や保守性を著しく低下させます。
本記事では、PythonのNLPライブラリを活用し、テキストの持つ言語的特徴(品詞、依存関係など)や構造的特徴(改行、インデント、特定のキーワード)を組み合わせることで、階層構造を持つテキストから効果的に情報を抽出する実践的な手法について解説します。
階層構造を持つテキスト抽出の課題
階層構造を持つテキストの例として、以下のような簡略化された議事録の一部を考えてみます。
## 第X回 会議議事録
### 議題1: プロジェクトAの進捗
担当: 山田
決定事項: フェーズ2への移行を承認。期日は来月末とする。
保留事項: 追加予算の件は次回持ち越し。
### 議題2: 新規開発Bについて
担当: 田中、佐藤
決定事項: 要件定義フェーズを開始。まずはPoCを実施。
懸念事項: 既存システムとの連携負荷。
このテキストから、「議題」「担当」「決定事項」「保留事項/懸念事項」といった情報を、それぞれの議題と関連付けながら構造化されたデータ(例:JSONや辞書)として抽出したいとします。
単純な正規表現で「担当: (.*)」のようなパターンを抽出することは可能ですが、それが「議題1」の担当なのか「議題2」の担当なのかを正確に区別し、関連付けるのは困難です。見出し (###
)、改行、キーワード (担当:
, 決定事項:
) など、複数の要素が組み合わさって階層やグループが形成されているため、これらの構造を同時に捉える必要があります。
NLPライブラリを用いたアプローチ
NLPライブラリは、テキストを単なる文字列としてではなく、単語(トークン)、品詞、文の構造(依存関係)など、より豊かな言語情報として扱えるようにします。この情報を活用することで、テキストの論理的な構造を推測し、階層的な関係性を捉える手がかりを得ることができます。
主な活用ポイントは以下の通りです。
- トークン化と文分割: テキストを単語や句、文といった単位に分解します。これにより、キーワードや区切り記号を正確に特定できます。
- 品詞タグ付け (Part-of-Speech Tagging): 各単語の品詞(名詞、動詞、助詞など)を特定します。特定の品詞パターン(例: 担当者の名前は名詞である可能性が高い)をルールに組み込めます。
- 依存構造解析 (Dependency Parsing): 文中の単語間の文法的な関係性(主語-述語、修飾語-被修飾語など)を解析します。これにより、「担当: 山田」のような表現において、「山田」が「担当」というキーワードの対象である、といった関係性を捉えることが可能になります。
- 固有表現抽出 (Named Entity Recognition - NER): 固有名詞(人名、組織名、日付など)を識別します。「山田」や「来月末」といった情報を自動的に特定できます。
- 構造情報の利用: NLPライブラリの処理結果に加え、改行、インデント、特定の記号、見出しパターン(例:
##
,###
)といったテキスト自体の物理的な構造情報も、階層を区別する重要な手がかりとなります。
これらの情報を組み合わせ、「見出し (###
) で始まる行は新しい議題の開始を示す」「担当:
というキーワードの後に続く名詞句はその議題の担当者である」「改行までが一つの項目の内容である」といったルールを組み立てて情報を抽出していきます。
PythonとSpaCyによる実装例
ここでは、広く使われているPythonのNLPライブラリであるSpaCyを使用した実装例を示します。SpaCyは高速かつ高機能であり、依存構造解析や固有表現抽出を容易に実行できます。
まず、SpaCyをインストールします。
pip install spacy
python -m spacy download ja_core_news_sm # 日本語モデルのダウンロード
上記の議事録例を処理し、構造化されたデータとして抽出するコードを考えます。
import spacy
import re
# 日本語モデルのロード
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_hierarchical_minutes(text):
"""
簡略化された議事録テキストから階層構造を持つ情報を抽出する。
Args:
text (str): 入力議事録テキスト。
Returns:
list: 各議題の情報を辞書として格納したリスト。
"""
lines = text.strip().split('\n')
minutes_data = []
current_agenda = None
current_item_key = None
item_content_lines = []
# 行ごとに処理
for line in lines:
line = line.strip()
# 見出し行の判定 (議題の開始)
agenda_match = re.match(r'^###\s*(.+)', line)
if agenda_match:
# 前の議題の情報があれば保存
if current_agenda:
if current_item_key and item_content_lines:
current_agenda[current_item_key] = " ".join(item_content_lines).strip()
minutes_data.append(current_agenda)
# 新しい議題の開始
agenda_title = agenda_match.group(1).strip()
current_agenda = {"議題": agenda_title}
current_item_key = None # 議題タイトル以外はリセット
item_content_lines = []
continue # 見出し行は次の処理に移る
# 議題内の項目行の判定 (例: 担当:, 決定事項:)
item_match = re.match(r'^(.+?):\s*(.+)', line)
if item_match:
# 前の項目の内容があれば保存
if current_item_key and item_content_lines:
current_agenda[current_item_key] = " ".join(item_content_lines).strip()
# 新しい項目の開始
current_item_key = item_match.group(1).strip()
item_content_lines = [item_match.group(2).strip()] # 最初の内容を設定
continue # 項目開始行は次の処理に移る
# 項目の内容行 (項目開始行に続いている場合)
# SpaCyを使って文構造などを分析し、この行が直前の項目に属するかを判断する
# 例: 空行でないこと、見出しパターンでないことなど
if current_item_key and line: # 空行でなく、現在の項目キーが存在する場合
# SpaCyを使ったより高度な判断も可能だが、ここではシンプルに非空行を内容とみなす
# doc = nlp(line)
# if not line.startswith("##") and not line.startswith("###"): # より堅牢にする例
item_content_lines.append(line)
# doc = nlp(line)
# for token in doc:
# print(f"{token.text} {token.pos_} {token.dep_}") # 依存関係などをデバッグ表示可能
# 最後の議題の情報があれば保存
if current_agenda:
if current_item_key and item_content_lines:
current_agenda[current_item_key] = " ".join(item_content_lines).strip()
minutes_data.append(current_agenda)
return minutes_data
# サンプルテキスト
minutes_text = """
## 第X回 会議議事録
日時: 2023年10月27日 10:00-11:00
場所: オンライン会議室
参加者: 山田、田中、佐藤
### 議題1: プロジェクトAの進捗
担当: 山田
決定事項: フェーズ2への移行を承認。
期日は来月末とする。
保留事項: 追加予算の件は次回持ち越し。
関連資料の準備が必要。
### 議題2: 新規開発Bについて
担当: 田中、佐藤
決定事項: 要件定義フェーズを開始。
まずはPoCを実施。
懸念事項: 既存システムとの連携負荷。
セキュリティ要件の確認も必要。
### 議題3: その他
特になし。
"""
extracted_data = extract_hierarchical_minutes(minutes_text)
import json
print(json.dumps(extracted_data, indent=2, ensure_ascii=False))
このコードでは、行ベースの処理と簡単な正規表現、そして「直前の項目キーに続く非空行は内容の一部である」というルールを組み合わせています。SpaCy自体は直接的な構造解析には使っていませんが、各行や項目の内容に対して nlp(line)
のように適用することで、個々の文の依存構造を調べたり、固有表現を抽出したりすることが可能です。例えば、「担当: 山田」の「山田」が人名であることや、「来月末」が日付表現であることをSpaCyのNER機能で確認し、抽出の信頼性を高めることができます。
より複雑な構造や曖昧な表現に対応するには、SpaCyの依存構造解析結果を利用し、「担当
という単語に nsubj
(主語) や obj
(目的語) としてリンクしている名詞句を抽出する」といった、言語的なルールを組み合わせるアプローチが有効です。
実務への応用例と考慮事項
応用例
- 議事録からの決定事項・ToDo抽出: 誰が何を決定したか、誰がいつまでに何をすべきか、といった情報を議題ごとに整理して抽出。タスク管理システムとの連携に活用。
- 技術仕様書からの設定項目抽出: セクションごとの設定項目、許容される値、デフォルト値などを抽出。設定ファイルの自動生成やバリデーションルール作成に利用。
- 顧客レビューからの要望整理: 製品機能やサービス項目ごとに、改善要望や良い点・悪い点をまとめて抽出。製品改善計画の立案に活用。
- FAQテキストからの質問と回答ペア抽出: 階層化されたFAQドキュメントから、質問とその回答をペアで抽出し、チャットボットの学習データとして利用。
考慮事項
- テキストフォーマットの多様性: 実際のテキストは、見出しレベル、区切り文字(: , - など)、インデント方法、改行の有無などに大きな揺れがあることがあります。ある程度定型化されているテキストに対しては有効ですが、完全に自由記述のテキストには適用が難しい場合があります。
- ルールの複雑化と保守性: カバーすべきパターンが増えるにつれて、ルールが複雑になり、保守が困難になる傾向があります。重要な情報や頻出パターンに絞って適用したり、少量の教師データを用いて学習ベースのアプローチ(後述)と組み合わせたりすることも検討が必要です。
- パフォーマンス: 非常に大規模なテキストデータに対して、行ごとのNLP処理は時間がかかる可能性があります。必要に応じて、処理を高速化するための工夫(並列処理など)や、より軽量な手法の検討が必要になる場合があります。
- 曖昧性の問題: 人間が見ても解釈が分かれるような曖昧な表現や構造は、ルールベースの手法では正確に抽出できない可能性が高いです。
より高度なアプローチ
本記事で紹介したルールベースのアプローチは、特定の構造が比較的明確なテキストに対して効果的です。しかし、より複雑で多様な階層構造や、曖昧な表現に対応するには、機械学習を組み合わせたアプローチも考えられます。
- シーケンスラベリング: テキスト中の各トークンに対し、「議題の開始」「担当者」「決定事項の内容」といったラベルを付与する問題として捉え、CRF (Conditional Random Field) や Bi-LSTM-CRF といったモデルで学習・予測を行います。これは固有表現抽出と似た枠組みで実現できます。
- グラフ構造への変換: テキストをノード(単語、句、文など)とエッジ(依存関係、隣接関係、キーワードとの関連など)を持つグラフとして表現し、グラフ構造解析によって特定のパターンやサブグラフを抽出する手法も研究されています。
これらの機械学習アプローチは、多量の教師データが必要となることが多いですが、様々なフォーマットや表現の揺れに対してより頑健なモデルを構築できる可能性があります。
まとめ
本記事では、議事録や仕様書など、見出しや項目分けによって階層構造を持つ業務テキストから情報を抽出するための、PythonとNLPライブラリを活用した実践的な手法を解説しました。
単なる正規表現では対応が難しい複雑な構造に対しても、SpaCyのようなNLPライブラリを用いることで、テキストの言語的・構造的特徴を捉え、より柔軟かつ高精度なルールを構築することが可能になります。行ベースの処理、キーワードマッチ、そしてNLPによる文構造解析などを組み合わせることで、非構造化または半構造化されたテキストから、目的の情報を含む構造化データを抽出できます。
もちろん、テキストの多様性によってはルールの設計やメンテナンスが課題となる場合もありますが、基本的な構造を持つ業務テキストに対しては、今回紹介したアプローチが強力なツールとなり得ます。ぜひ、実際の業務課題への適用を検討してみてください。