契約分析のための NLP: OCR から理解まで
毎年、世界中の企業が数十億件の契約を管理しています。供給契約、条項 機密保持、サービス条件、雇用契約: 伝統的に法的文書の海 弁護士やパラリーガルによる何百時間もの手動レビューが必要です。 2026 年の市場は グローバルの リーガルテック 需要に牽引されて 350 億ドルを超えた 法的文書管理の自動化が進んでいます。
本当の革命は単に文書をデジタル化することではなく、 それらを理解する。 スキャンした80ページの契約書のPDFを構造化データに変換し、自動的に抽出 契約当事者、義務、期限、リスク条項: これは、 自然言語処理 (NLP) 法的領域に適用されます。
この記事でシリーズをスタートします リーガルテックとAI 完全なパイプラインを探索する 紙の文書を実用的な知識に変換します。OCR (光学文字 認識)アル NER (固有表現の認識)、から の分類 条項 alla 意味的理解。実際の Python コードを見て比較します。 主要な OCR エンジンと NLP モデルを分析し、それらが再定義している LegalTech プラットフォームを分析します セクター。
この記事で学べること
- 完全な契約分析パイプライン: 物理文書 → OCR → NLP → 構造化された洞察
- OCR エンジンの比較: Tesseract、AWS Textract、Azure Document Intelligence、Google Document AI
- ドキュメントの前処理: レイアウト分析、テーブル抽出、ページ分割
- 契約の指定主体認識 (NER): 当事者、日付、金額、条項
- Transformer モデルによる条項の分類 (LegalBERT、DeBERTa)
- spaCy、HuggingFace Transformers、Tesseract を使用した完全な Python 実装
- 実際の LegalTech プラットフォーム: Kira Systems、Luminance、Ironclad
- Contract AI プロジェクトの評価指標とベスト プラクティス
LegalTech と AI シリーズの概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | あなたはここにいます — 契約分析のための NLP | OCRから理解へ |
| 2 | AIを活用した法学研究 | セマンティック検索とナレッジグラフ |
| 3 | スマートコントラクトとブロックチェーン | ブロックチェーンによる契約の自動化 |
| 4 | 法的文書の要約 | LLM による自動要約 |
| 5 | AIを活用したコンプライアンスエンジン | 自動化された規制遵守 |
| 6 | 電子情報開示とフォレンジック | 紛争の文書分析 |
| 7 | 電子署名とデジタル ID | eIDAS 2.0 と SPID |
| 8 | GDPR とプライバシーバイデザイン | AIによるプライバシーの遵守 |
| 9 | 法務副操縦士 | 法律事務所向けAIアシスタント |
| 10 | 法的データの統合 | 相互運用性と標準 |
契約分析パイプライン
契約の自動分析は単一のアルゴリズムではなく、 多段パイプライン 各コンポーネントが次のコンポーネントに電力を供給します。まず全体的かつ基本的なアーキテクチャを理解する 各フェーズの技術的な詳細に没頭できます。
パイプラインの 6 つのフェーズ
- 取得: ドキュメントがシステムに入力されます (スキャン、アップロード、電子メール、DMS API)
- 前処理と OCR: レイアウト解析、デスキュー、二値化、テキスト抽出
- 構造化: セクション、段落、節、表への分割
- エンティティ抽出 (NER): 部品、日付、数量、規制参照の識別
- 分類と分析: 条項の種類、リスクのレベル、義務、条件
- 出力と統合: 構造化JSON、ダッシュボード、アラート、CLM/DMSとの統合
各フェーズでは、特定の課題が発生します。 OCR のエラーがカスケードして NER が役に立たなくなる より洗練された。セグメント化が正しくないと、免責事項が分類される可能性があります 簡単な定義として。このため、パイプラインの品質が測定されます。 エンドツーエンド、 コンポーネントごとではありません。
| 段階 | 入力 | 出力 | キーテクノロジー |
|---|---|---|---|
| 取得 | PDF、TIFF、DOCX、画像 | 正規化されたファイル | Apache Tika、python-docx、PyPDF2 |
| OCR | スキャン画像/PDF | 生のテキスト + 座標 | Tesseract、Textract、ドキュメント AI |
| 構造化 | 生のテキスト | セクション、条項、表 | レイアウトパーサー、正規表現、ルールエンジン |
| NER | 構造化されたテキスト | 注釈付きエンティティ | spaCy、Legal-NER、John Snow Labs |
| 分類 | 孤立した条項 | ラベル + 信頼スコア | LegalBERT、DeBERTa、GPT-4 |
| 出力 | 構造化データ | JSON、ダッシュボード、アラート | REST API、Webhook、CLM SDK |
法的文書の OCR: テクノロジーの比較
OCR はパイプラインの最初のボトルネックです。契約書はネイティブ PDF (テキスト付き) として受け取ることができます。 選択可能)、または紙文書のスキャンとして。 2 番目のケースでは、OCR を再構築する必要があります。 ピクセルからのテキスト、可変スキャン品質の管理、高密度のリーガルフォント、 2 列のレイアウト、表、手書きの注釈。
2025 年から 2026 年にかけて、OCR 環境は 4 つのエンタープライズ プラットフォームと 1 つのオープンソース プロジェクトによって支配される これは依然として重要な参考資料です。違いを見てみましょう。
Tesseract OCR (オープンソース)
テッセラクト、Google によって保守されており、現在はバージョン 5.x であり、オープンソースの OCR エンジンです。 世界で最も普及しています。文字認識に LSTM ネットワークを使用し、それ以外の機能をサポートします。 100の言語。その強度と柔軟性: あらゆる Python パイプラインに統合できます。 ライセンスコストを削減し、前処理を完全に制御できます。
ただし、Tesseract には、複雑な法的文書に対して重大な制限があります。ネイティブでは扱えない テーブル抽出ではレイアウト構造 (ヘッダー、フッター、列) が認識されません。 これを達成するには、大幅な前処理 (2 値化、デスキュー、ノイズ除去) が必要です。 中品質のスキャンでは許容可能な結果。
AWS テキストトラクト
Amazon テキストトラクト の抽出を提供することで、従来の OCR を超えています。 テーブル, フォーム (キーと値のペア) e 署名 ネイティブ機能として。統合 AWS エコシステム (S3、Lambda、Step Functions) と組み合わせることで、大容量のサーバーレス パイプラインに最適になります。 非同期処理を使用すると、数百ページのドキュメントをタイムアウトせずに処理できます。
Azure AI ドキュメント インテリジェンス
Azure AI ドキュメント インテリジェンス (以前の Form Recognizer) は、事前トレーニングされたモデルを提供します。 請求書、領収書、身分証明書、および強力な情報 レイアウトテンプレート それ 段落、表、選択マーク、バーコードを抽出します。強さと可能性 訓練する カスタムモデル ドメイン固有の文書、基本的な文書 独自のレイアウトを使用した契約の場合。
GoogleドキュメントAI
GoogleドキュメントAI 非常に高精度の OCR と専用プロセッサを組み合わせます 特定のドキュメントタイプ用。の ドキュメントOCRプロセッサ 精度を実現します 98% はテキストですが、カスタム プロセッサは特定のフィールドを抽出するようにトレーニングできます 標準化された契約から。 Vertex AI との統合により、エンドツーエンドの ML パイプラインを構築できます。
法的文書用の OCR エンジンの比較
| 特性 | テッセラクト5 | AWS テキストトラクト | Azure ドキュメント インテリジェンス | GoogleドキュメントAI |
|---|---|---|---|---|
| テキストの正確さ | 92-95% | 96-98% | 97-99% | 98%以上 |
| テーブルの抽出 | ネイティブではありません | はい(ネイティブ) | はい(ネイティブ) | はい(ネイティブ) |
| レイアウト分析 | 基本 | 良い | 素晴らしい | 素晴らしい |
| カスタムモデル | OCRトレーニング | カスタムクエリ | カスタムモデル | カスタムプロセッサ |
| 料金 | 無料(OSS) | $1.50/1000 ページ | $1.50/1000 ページ | $1.50/1000 ページ |
| オンプレミス | Si | No | はい(コンテナ) | No |
| サポートされている言語 | 100+ | ~20 | ~300 | ~200 |
| 手書き | 限定 | 良い | 良い | 良い |
| に最適 | プロトタイピング、低予算 | AWSサーバーレスパイプライン | エンタープライズマイクロソフト | 高精度、GCP |
OCRが必要ない場合
最近の契約書の多くはデジタル (Word、ネイティブ PDF) で作成されています。このような場合、OCR e 余分で有害な可能性があります: のようなライブラリを使用してネイティブ PDF からテキストを直接抽出します。 PyMuPDF (フィッツ) または pdf配管工 より速く、より正確に、しかもコストはかかりません。 パイプラインの最初の操作は常に次のようにする必要があります。 トリアージ: かどうかを決定します。 ドキュメントに OCR が必要な場合、またはテキストがすでに利用可能な場合。
実装: Python での OCR パイプライン
ネイティブ PDF とスキャンの両方を処理する堅牢な OCR パイプラインを Python で構築する方法を見てみましょう。 自動前処理と異なるエンジン間のフォールバックを備えています。
文書の優先順位付けと前処理
最初のステップは、PDF に選択可能なテキストが含まれているかどうか、またはスキャンされた画像であるかを判断することです。 この決定により、後続のパイプライン全体が推進されます。
import fitz # PyMuPDF
import pytesseract
from PIL import Image, ImageFilter, ImageEnhance
from pathlib import Path
from dataclasses import dataclass
from typing import Optional
import io
@dataclass(frozen=True)
class OcrResult:
"""Risultato immutabile dell'estrazione testo."""
text: str
source: str # 'native' | 'tesseract' | 'textract'
confidence: float # 0.0 - 1.0
page_count: int
tables: list # tabelle estratte
def is_native_pdf(pdf_path: str, threshold: float = 0.1) -> bool:
"""Determina se un PDF contiene testo selezionabile.
Args:
pdf_path: Percorso al file PDF
threshold: Rapporto minimo caratteri/pagina per considerarlo nativo
Returns:
True se il PDF ha testo estraibile direttamente
"""
doc = fitz.open(pdf_path)
total_chars = sum(len(page.get_text()) for page in doc)
avg_chars_per_page = total_chars / len(doc) if len(doc) > 0 else 0
doc.close()
# Un contratto tipico ha 2000-5000 caratteri per pagina
return avg_chars_per_page > 100
def extract_native_text(pdf_path: str) -> OcrResult:
"""Estrae testo da PDF nativo senza OCR."""
doc = fitz.open(pdf_path)
pages_text = []
for page in doc:
pages_text.append(page.get_text("text"))
full_text = "\n\n".join(pages_text)
page_count = len(doc)
doc.close()
return OcrResult(
text=full_text,
source="native",
confidence=0.99,
page_count=page_count,
tables=[]
)
def preprocess_image(image: Image.Image) -> Image.Image:
"""Preprocessing per migliorare la qualità OCR.
Applica: conversione grayscale, contrasto, nitidezza,
binarizzazione adattiva.
"""
# Conversione in scala di grigi
gray = image.convert("L")
# Aumento contrasto
enhancer = ImageEnhance.Contrast(gray)
enhanced = enhancer.enhance(2.0)
# Aumento nitidezza
sharpened = enhanced.filter(ImageFilter.SHARPEN)
# Binarizzazione con soglia adattiva
threshold_value = 128
binary = sharpened.point(
lambda x: 255 if x > threshold_value else 0, "1"
)
return binary
def ocr_with_tesseract(
pdf_path: str,
lang: str = "ita+eng"
) -> OcrResult:
"""OCR con Tesseract e preprocessing avanzato."""
doc = fitz.open(pdf_path)
pages_text = []
confidences = []
for page_num in range(len(doc)):
page = doc[page_num]
# Renderizza pagina come immagine ad alta risoluzione
mat = fitz.Matrix(300 / 72, 300 / 72) # 300 DPI
pix = page.get_pixmap(matrix=mat)
img_bytes = pix.tobytes("png")
image = Image.open(io.BytesIO(img_bytes))
# Preprocessing
processed = preprocess_image(image)
# OCR con dati di confidenza
ocr_data = pytesseract.image_to_data(
processed,
lang=lang,
output_type=pytesseract.Output.DICT,
config="--oem 3 --psm 6"
)
# Estrai testo e calcola confidenza media
page_text = pytesseract.image_to_string(
processed, lang=lang,
config="--oem 3 --psm 6"
)
pages_text.append(page_text)
# Confidenza media (escludi valori -1)
valid_confs = [
int(c) for c in ocr_data["conf"] if int(c) > 0
]
if valid_confs:
confidences.append(sum(valid_confs) / len(valid_confs))
doc.close()
avg_confidence = (
sum(confidences) / len(confidences) / 100
if confidences else 0.0
)
return OcrResult(
text="\n\n".join(pages_text),
source="tesseract",
confidence=avg_confidence,
page_count=len(pages_text),
tables=[]
)
def process_contract(pdf_path: str) -> OcrResult:
"""Pipeline principale: triage + estrazione."""
if is_native_pdf(pdf_path):
return extract_native_text(pdf_path)
return ocr_with_tesseract(pdf_path)
ベスト プラクティス: DPI と前処理
小さなフォントと高密度のレイアウトを含む法律文書の場合は、常に少なくとも DPI を使用してください。 300 ラスタライズ用。前処理(二値化、デスキュー、エッジ除去) 平均品質スキャンで OCR 精度を 10 ~ 15% 向上させることができます。ただし、スキャンでは 高品質で積極的な前処理が可能 悪化する 結果: 常に、 代表的なサンプル。
法文の NLP: 具体的な課題
法的言語は通常の自然言語ではありません。を実現する機能を備えています。 自動処理は、最新のモデルであっても特に困難です。
法律用語の複雑さ
- 長くて複雑な文: 単一の契約条項は次の期間まで延長できます 複数の下位、彫刻、相互参照を含む 200 ~ 500 語。変圧器モデル 制限されたコンテキスト ウィンドウ (BERT の場合は 512 トークン) では、グローバルな意味が失われる可能性があります。
- 専門用語: 「明示的終了条項」、「刑事」などの用語 「契約上の」、「例外的な契約」は、用法とは異なる正確な意味を持ちます。 同じ言葉の共通点。
- 意図的な曖昧さ: 一部の条項は許可するために意図的に曖昧になっています 柔軟な解釈。 NLP はこの曖昧さを解決するのではなく、認識して報告する必要があります。 勝手に。
- 相互参照: 「第 5 条第 3 項 b) に従って」には、次のことが要求されます。 ドキュメントへの内部および外部参照の解決。
- 複数の否定的な条件と条件: 「前項に規定する場合を除き、 the party will not be required to...」は、テキストのブロック全体の意味を逆転させます。
- 多言語主義: 国際契約には複数の言語で条項が含まれる場合があります。 言語的普及条項を使用すると、さらに複雑さが増します。
法的領域の NLP モデル
汎用言語モデル (BERT、RoBERTa) の法的文章に対するパフォーマンスは最適ではありません なぜなら、彼らの事前トレーニングはウィキペディア、書籍、一般的なウェブページに基づいているからです。これが彼らが生まれた理由です モデル ドメイン固有の 法的コーパスについて訓練を受けています。
| モデル | 基本 | トレーニング コーパス | 言語 | 主な用途 |
|---|---|---|---|---|
| 法的BERT | BERT-基本 | 12GB の法的テキスト (EU、英国、米国) | 英語 | NER、条項分類 |
| 判例-BERT | BERT-基本 | アメリカの判例法(ハーバード大学) | 英語 | 法学研究 |
| イタリア法-BERT | BERT-基本 | IT法と法学 | イタリア語 | NER イタリアの法人 |
| LegalPro-BERT | BERT-基本 | 専門的な法的規定 | 英語 | 規定の分類 |
| DeBERTa-v3-合法 | DeBERTa-v3 | 契約+法律 | 英語 | 法的質問への回答 |
| John Snow Labs 法的 NLP | スパークNLP | 600 以上の法律に特化したテンプレート | 多言語対応 | 完全なエンタープライズ パイプライン |
CUAD (Contract Understanding Atticus Dataset) などの特定のデータセットに対する LegalBERT の微調整 41 種類の契約条項の分類で 83 ~ 88% の F1 スコアに達します。 同じタスクにおいて、一般的な BERT よりも 12 ~ 15% 向上します。ベーシックモデルの選択 そして、微調整データの品質と同じくらい重要です。
契約の指定主体の認識
Il 固有表現認識 (NER) システムが識別して分類するフェーズ 契約文の主要なエンティティ。人や場所を認識する汎用NERとは異なります。 および組織では、法的 NER はドメイン固有のエンティティを抽出する必要があります。
主要な法人
| 実在物 | 説明 | Esempio |
|---|---|---|
| パーティー | 契約当事者 | 「アクメS.p.A.」 (サプライヤー)、「Beta S.r.l.」 (お客様) |
| 日付 | 関連する日付 | 署名日、発効日、有効期限、更新 |
| AMOUNT | 金額 | 「年間 150,000 ユーロ」、「50,000 米ドル」 |
| 間隔 | 期間 | 「36か月」、「署名日から3年」 |
| CLAUSE_REF | 条項への参照 | 「第5条第3項」、「第12.1条」 |
| LAW_REF | 規制に関する参考資料 | 「立法令 50/2016」、「条項 1341 c.c.」 |
| 義務 | 契約上の義務 | 「サプライヤーは...を配達することを約束します。」 |
| 管轄 | 管轄裁判所 | 「ミラノ法廷」 |
| 統治法 | 準拠法 | 「イタリア法」、「イギリス法」 |
spaCyによるNER実装
spaCy は、カスタム NER パイプラインを構築するための柔軟なフレームワークを提供します。作成方法を見てみましょう 決定論的なルールと機械学習を組み合わせた、イタリアの契約に特化した NER モデル。
import spacy
from spacy.tokens import Span
from spacy.language import Language
from spacy.matcher import Matcher, PhraseMatcher
from dataclasses import dataclass
from typing import Tuple
@dataclass(frozen=True)
class LegalEntity:
"""Entità legale estratta dal contratto."""
text: str
label: str
start_char: int
end_char: int
confidence: float
@Language.component("legal_entity_ruler")
def legal_entity_ruler(doc):
"""Componente spaCy per entità legali con regole."""
new_ents = list(doc.ents)
# Pattern per riferimenti normativi italiani
law_patterns = [
# D.Lgs. 50/2016, D.L. 18/2020
r"D\.(?:Lgs|L|P\.R|M)\.\s*\d+/\d{4}",
# Art. 1341 c.c., Art. 2043 c.c.
r"[Aa]rt(?:icolo|\.)\s*\d+(?:\s*(?:comma|co\.)\s*\d+)?(?:\s*c\.c\.|"
r"\s*c\.p\.c\.|c\.p\.)?",
# Legge n. 241/1990
r"[Ll]egge\s*(?:n\.\s*)?\d+/\d{4}",
]
# Pattern per importi monetari
amount_patterns = [
# EUR 150.000,00 / euro 150.000
r"(?:EUR|euro|Euro|€)\s*[\d.,]+",
# 150.000,00 euro
r"[\d.]+,\d{2}\s*(?:EUR|euro|Euro|€)",
# USD 50,000.00
r"(?:USD|GBP|CHF|\$|£)\s*[\d,]+(?:\.\d{2})?",
]
# Pattern per date italiane
date_patterns = [
# 15 marzo 2026, 1 gennaio 2025
r"\d{1,2}\s+(?:gennaio|febbraio|marzo|aprile|maggio|giugno|"
r"luglio|agosto|settembre|ottobre|novembre|dicembre)\s+\d{4}",
# 15/03/2026, 01.01.2025
r"\d{2}[/.\-]\d{2}[/.\-]\d{4}",
]
import re
for pattern in law_patterns:
for match in re.finditer(pattern, doc.text):
span = doc.char_span(
match.start(), match.end(), label="LAW_REF"
)
if span and not any(
ent.start <= span.start < ent.end
for ent in new_ents
):
new_ents.append(span)
for pattern in amount_patterns:
for match in re.finditer(pattern, doc.text):
span = doc.char_span(
match.start(), match.end(), label="AMOUNT"
)
if span and not any(
ent.start <= span.start < ent.end
for ent in new_ents
):
new_ents.append(span)
for pattern in date_patterns:
for match in re.finditer(pattern, doc.text):
span = doc.char_span(
match.start(), match.end(), label="DATE"
)
if span and not any(
ent.start <= span.start < ent.end
for ent in new_ents
):
new_ents.append(span)
doc.ents = sorted(new_ents, key=lambda e: e.start)
return doc
def create_legal_nlp() -> spacy.Language:
"""Crea pipeline spaCy per NER legale."""
# Carica modello italiano base
nlp = spacy.load("it_core_news_lg")
# Aggiungi componente custom dopo il NER standard
nlp.add_pipe("legal_entity_ruler", after="ner")
return nlp
def extract_entities(
text: str,
nlp: spacy.Language
) -> list:
"""Estrai entità legali dal testo contrattuale."""
doc = nlp(text)
return [
LegalEntity(
text=ent.text,
label=ent.label_,
start_char=ent.start_char,
end_char=ent.end_char,
confidence=0.85 # spaCy rule-based = high confidence
)
for ent in doc.ents
if ent.label_ in (
"PER", "ORG", "LOC", "DATE",
"AMOUNT", "LAW_REF", "PARTY"
)
]
# Esempio di utilizzo
nlp = create_legal_nlp()
sample = """
Il presente contratto e stipulato tra Acme S.p.A., con sede
in Via Roma 15, Milano (di seguito "Fornitore") e Beta S.r.l.,
con sede in Via Napoli 42, Roma (di seguito "Cliente").
Il corrispettivo annuo e pari a EUR 150.000,00 ai sensi
dell'Art. 1655 c.c. La durata e di 36 mesi a decorrere
dal 1 marzo 2026.
"""
entities = extract_entities(sample, nlp)
for entity in entities:
print(f"{entity.label:<12} {entity.text}")
期待される出力
ORG Acme S.p.A.
LOC Via Roma 15, Milano
ORG Beta S.r.l.
LOC Via Napoli 42, Roma
AMOUNT EUR 150.000,00
LAW_REF Art. 1655 c.c.
DATE 1 marzo 2026
変圧器に関する条項の分類
条項の分類は、自動契約分析の中心です。すべての条項 カテゴリ (責任、機密保持、解雇、罰則など) に割り当てられます。 オプションでリスクレベルを評価します。このプロセスには伝統的に何時間もかかります 法的業務の処理: トレーニングされた Transformer モデルが数秒でそれを実行します。
CUAD データセット
Il 契約理解アティカス データセット (CUAD) および基準ベンチマーク 契約上の分類。数十人の弁護士の協力を得て、The Atticus Project によって作成されました。 もっと多く含まれています 13,000の注釈 510件の実際のビジネス契約をカバー M&A取引やデューデリジェンスに関わる条項は41種類。
41 CUAD カテゴリ (セレクション)
- 契約日
- パーティー
- 準拠法
- 都合による終了
- アンチアサインメント
- 非競争
- 非勧誘
- 独占性
- 補償
- 責任の制限
- IP所有権の割り当て
- ライセンスの付与
- 機密保持契約
- 収益/利益の分配
- 価格制限
- 最低限の約束
- 音量制限
- 保険
- 監査権
- コントロールの変更
条項分類のための LegalBERT の微調整
LegalBERT と CUAD データセットを使用して文節分類器をトレーニングする方法を見てみましょう。 このプロセスには、注釈付きの文節の例に基づいて事前トレーニングされたモデルを微調整することが含まれます。 契約上の。
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
)
from datasets import load_dataset, Dataset
from sklearn.metrics import f1_score, precision_score, recall_score
import numpy as np
from dataclasses import dataclass
from typing import Dict
@dataclass(frozen=True)
class ClauseLabel:
"""Label per la classificazione delle clausole."""
id: int
name: str
risk_level: str # 'low' | 'medium' | 'high' | 'critical'
# Definizione categorie di clausole
CLAUSE_LABELS = {
0: ClauseLabel(0, "termination", "high"),
1: ClauseLabel(1, "indemnification", "critical"),
2: ClauseLabel(2, "limitation_of_liability", "critical"),
3: ClauseLabel(3, "confidentiality", "medium"),
4: ClauseLabel(4, "non_compete", "high"),
5: ClauseLabel(5, "governing_law", "medium"),
6: ClauseLabel(6, "intellectual_property", "high"),
7: ClauseLabel(7, "payment_terms", "medium"),
8: ClauseLabel(8, "warranty", "high"),
9: ClauseLabel(9, "force_majeure", "medium"),
10: ClauseLabel(10, "assignment", "medium"),
11: ClauseLabel(11, "dispute_resolution", "medium"),
12: ClauseLabel(12, "general_provision", "low"),
}
MODEL_NAME = "nlpaueb/legal-bert-base-uncased"
def load_and_prepare_data(
dataset_name: str = "theatticusproject/cuad-qa"
) -> tuple:
"""Carica e prepara il dataset CUAD per classificazione."""
dataset = load_dataset(dataset_name)
# Trasforma da QA a classificazione
texts = []
labels = []
for example in dataset["train"]:
context = example["context"]
# Usa la categoria della domanda come label
category = example.get("category", "general_provision")
label_id = next(
(k for k, v in CLAUSE_LABELS.items()
if v.name == category),
12 # default: general_provision
)
texts.append(context[:512]) # Tronca a 512 token
labels.append(label_id)
return texts, labels
def compute_metrics(eval_pred) -> Dict[str, float]:
"""Calcola metriche di valutazione."""
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return {
"f1_macro": f1_score(
labels, predictions, average="macro"
),
"f1_weighted": f1_score(
labels, predictions, average="weighted"
),
"precision": precision_score(
labels, predictions, average="weighted"
),
"recall": recall_score(
labels, predictions, average="weighted"
),
}
def train_clause_classifier():
"""Addestra il classificatore di clausole."""
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForSequenceClassification.from_pretrained(
MODEL_NAME,
num_labels=len(CLAUSE_LABELS),
problem_type="single_label_classification",
)
texts, labels = load_and_prepare_data()
# Tokenizzazione
encodings = tokenizer(
texts,
truncation=True,
padding="max_length",
max_length=512,
return_tensors="pt",
)
# Crea dataset HuggingFace
dataset = Dataset.from_dict({
"input_ids": encodings["input_ids"],
"attention_mask": encodings["attention_mask"],
"labels": labels,
})
# Split train/eval
split = dataset.train_test_split(test_size=0.2, seed=42)
training_args = TrainingArguments(
output_dir="./legal-bert-clauses",
num_train_epochs=5,
per_device_train_batch_size=16,
per_device_eval_batch_size=32,
warmup_steps=500,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=100,
eval_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="f1_macro",
learning_rate=2e-5,
fp16=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=split["train"],
eval_dataset=split["test"],
compute_metrics=compute_metrics,
)
trainer.train()
return trainer
# Inferenza su nuove clausole
def classify_clause(
text: str,
model,
tokenizer
) -> tuple:
"""Classifica una singola clausola contrattuale.
Returns:
Tuple di (label, confidence, risk_level)
"""
inputs = tokenizer(
text,
truncation=True,
padding="max_length",
max_length=512,
return_tensors="pt",
)
outputs = model(**inputs)
probs = outputs.logits.softmax(dim=-1)
predicted_id = probs.argmax().item()
confidence = probs.max().item()
clause_label = CLAUSE_LABELS[predicted_id]
return clause_label.name, confidence, clause_label.risk_level
CUAD での微調整の制限
CUAD データセットは、以下の商業契約に焦点を当てています。 英語 そして文脈の中で アメリカのM&A。イタリア、ヨーロッパ、またはその他の管轄区域の契約の場合、これは必要です 特定のデータセットを作成または適応させます。効果的なアプローチとしては、 2段階転移学習: CUAD で微調整して一般的なコントラクト構造を学習し、その後、さらに微調整します。 特定のドメインの小さなデータセット (200 ~ 500 個の例)。
義務の検出と意味分析
契約 AI システムは、条項を分類するだけでなく、条項を識別する必要があります。 義務 契約上の: 誰が何をいつまでにしなければならないのか、その結果はどうなるのか コンプライアンス違反のこと。このタスクは次のように知られています Deontic モダリティ検出 で 法的 NLP 文献。
3 つの神聖なモダリティ
- 義務 (しなければならない/しなければならない): 「サプライヤー しなければならない 30日以内に商品をお届けします。」
- 許可 (MAY/CAN): 「お客様 できる プロジェクトの変更をリクエストしてください」
- 禁止事項 (してはならない/してはならない): 「当事者たちは 彼らにはできません 契約を第三者に譲渡する」
ゼロショット分類による実装
義務の追跡については、効果的なアプローチは次のとおりです。 ゼロショット分類 DeBERTa のようなモデルでは、特定の注釈付きトレーニング データを必要としません。
from transformers import pipeline
from dataclasses import dataclass
from typing import List
@dataclass(frozen=True)
class Obligation:
"""Obbligo contrattuale rilevato."""
text: str
modality: str # 'obligation' | 'permission' | 'prohibition'
confidence: float
subject: str # chi ha l'obbligo
action: str # cosa deve fare
deadline: str # entro quando (se specificato)
def create_obligation_detector():
"""Crea il classificatore zero-shot per obblighi."""
return pipeline(
"zero-shot-classification",
model="MoritzLaurer/DeBERTa-v3-large-mnli-fever-"
"anli-ling-wanli",
device=0, # GPU, usa -1 per CPU
)
def detect_obligations(
clauses: List[str],
classifier
) -> List[Obligation]:
"""Rileva obblighi in una lista di clausole."""
candidate_labels = [
"obligation or duty",
"permission or right",
"prohibition or restriction",
"definition or description",
"condition or prerequisite",
]
results = []
for clause in clauses:
output = classifier(
clause,
candidate_labels,
multi_label=False,
)
top_label = output["labels"][0]
top_score = output["scores"][0]
# Filtra solo obblighi, permessi e divieti
modality_map = {
"obligation or duty": "obligation",
"permission or right": "permission",
"prohibition or restriction": "prohibition",
}
if top_label in modality_map and top_score > 0.6:
results.append(Obligation(
text=clause,
modality=modality_map[top_label],
confidence=top_score,
subject="", # Da estrarre con NER
action="", # Da estrarre con SRL
deadline="", # Da estrarre con NER
))
return results
# Esempio
classifier = create_obligation_detector()
clauses = [
"Il Fornitore deve consegnare i beni entro 30 giorni "
"dalla data dell'ordine.",
"Il Cliente può richiedere modifiche al progetto entro "
"la fase di design.",
"Le parti non possono cedere il presente contratto a "
"terzi senza previo consenso scritto.",
"Per 'Servizi' si intendono le attivita descritte "
"nell'Allegato A.",
]
obligations = detect_obligations(clauses, classifier)
for obl in obligations:
print(f"[{obl.modality.upper()}] "
f"({obl.confidence:.2f}) {obl.text[:60]}...")
文節間の意味上の類似性
もう 1 つの重要なアプリケーションは、文節間の意味論的な比較です。企業が新しいものを受け取ったとき 契約書について知りたい: 「この免責事項は当社の免責事項と似ているか、異なります。」 標準条項?」そこには 意味上の類似性 埋め込みに基づいてこの比較が可能になります。
from sentence_transformers import SentenceTransformer, util
from dataclasses import dataclass
from typing import List, Tuple
@dataclass(frozen=True)
class ClauseComparison:
"""Risultato del confronto tra clausole."""
clause_a: str
clause_b: str
similarity: float
is_substantially_similar: bool
deviation_areas: List[str]
def compare_clauses(
new_clause: str,
standard_clauses: List[str],
threshold: float = 0.85,
) -> List[ClauseComparison]:
"""Confronta una clausola con le clausole standard.
Args:
new_clause: Clausola dal contratto in revisione
standard_clauses: Clausole standard dell'azienda
threshold: Soglia di similarità (0-1)
Returns:
Lista di confronti ordinati per similarità
"""
model = SentenceTransformer(
"sentence-transformers/all-mpnet-base-v2"
)
new_embedding = model.encode(
new_clause, convert_to_tensor=True
)
std_embeddings = model.encode(
standard_clauses, convert_to_tensor=True
)
similarities = util.cos_sim(new_embedding, std_embeddings)
comparisons = []
for idx, sim_score in enumerate(similarities[0]):
score = sim_score.item()
comparisons.append(ClauseComparison(
clause_a=new_clause[:100],
clause_b=standard_clauses[idx][:100],
similarity=score,
is_substantially_similar=score >= threshold,
deviation_areas=(
[] if score >= threshold
else ["Possibile deviazione rilevata"]
),
))
# Ordina per similarità decrescente
return sorted(
comparisons, key=lambda c: c.similarity, reverse=True
)
# Esempio: confronto clausola di riservatezza
new_clause = (
"Le informazioni riservate non potranno essere divulgate "
"a terzi per un periodo di 5 anni dalla data di "
"risoluzione del contratto."
)
standard_clauses = [
"Le informazioni confidenziali non potranno essere "
"comunicate a terzi per 3 anni dalla cessazione del "
"rapporto contrattuale.",
"Il Fornitore si impegna a consegnare i prodotti entro "
"30 giorni lavorativi dall'ordine.",
"Le informazioni riservate saranno protette per un "
"periodo di 10 anni dalla firma del presente accordo.",
]
results = compare_clauses(new_clause, standard_clauses)
for r in results:
status = "SIMILE" if r.is_substantially_similar else "DIVERSA"
print(f"[{status}] Similarità: {r.similarity:.3f}")
print(f" Standard: {r.clause_b}...")
print()
エンドツーエンドのパイプライン: OCR から Insight まで
ここで、すべてのコンポーネントを、契約書 PDF を取得して出力する完全なパイプラインに統合します。 実体、分類された条項、特定された義務を含む構造化されたレポート。
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
import json
@dataclass(frozen=True)
class ContractAnalysis:
"""Risultato completo dell'analisi contrattuale."""
file_name: str
analyzed_at: str
page_count: int
ocr_source: str
ocr_confidence: float
parties: List[str]
dates: List[dict]
amounts: List[dict]
law_references: List[str]
clauses: List[dict]
obligations: List[dict]
risk_summary: dict
def analyze_contract(pdf_path: str) -> ContractAnalysis:
"""Pipeline completa di analisi contrattuale.
Fasi:
1. Triage e OCR
2. Segmentazione in clausole
3. NER per entità legali
4. Classificazione clausole
5. Rilevamento obblighi
6. Calcolo risk summary
"""
# Fase 1: OCR
ocr_result = process_contract(pdf_path)
# Fase 2: Segmentazione
clauses_text = segment_into_clauses(ocr_result.text)
# Fase 3: NER
nlp = create_legal_nlp()
all_entities = extract_entities(ocr_result.text, nlp)
parties = [
e.text for e in all_entities
if e.label in ("ORG", "PER", "PARTY")
]
dates = [
{"text": e.text, "position": e.start_char}
for e in all_entities if e.label == "DATE"
]
amounts = [
{"text": e.text, "position": e.start_char}
for e in all_entities if e.label == "AMOUNT"
]
law_refs = [
e.text for e in all_entities
if e.label == "LAW_REF"
]
# Fase 4: Classificazione clausole
classified = []
for clause in clauses_text:
label, conf, risk = classify_clause(
clause, model, tokenizer
)
classified.append({
"text": clause[:200],
"type": label,
"confidence": round(conf, 3),
"risk_level": risk,
})
# Fase 5: Rilevamento obblighi
obligation_detector = create_obligation_detector()
detected_obligations = detect_obligations(
clauses_text, obligation_detector
)
obligations_data = [
{
"text": o.text[:200],
"modality": o.modality,
"confidence": round(o.confidence, 3),
}
for o in detected_obligations
]
# Fase 6: Risk summary
risk_counts = {"low": 0, "medium": 0, "high": 0, "critical": 0}
for c in classified:
risk_counts[c["risk_level"]] = (
risk_counts.get(c["risk_level"], 0) + 1
)
return ContractAnalysis(
file_name=pdf_path,
analyzed_at=datetime.now().isoformat(),
page_count=ocr_result.page_count,
ocr_source=ocr_result.source,
ocr_confidence=round(ocr_result.confidence, 3),
parties=list(set(parties)),
dates=dates,
amounts=amounts,
law_references=list(set(law_refs)),
clauses=classified,
obligations=obligations_data,
risk_summary=risk_counts,
)
def segment_into_clauses(text: str) -> List[str]:
"""Segmenta il testo in clausole individuali.
Usa pattern tipici dei contratti italiani:
- Numerazione (1., 1.1, Art. 1, Articolo 1)
- Titoli in maiuscolo
- Separatori di sezione
"""
import re
# Pattern per inizio clausola
clause_pattern = re.compile(
r"(?:^|\n)"
r"(?:"
r"(?:Art(?:icolo)?\.?\s*\d+)" # Art. 1, Articolo 1
r"|(?:\d+\.\d*\s+[A-Z])" # 1. Titolo, 1.1 Sotto
r"|(?:[A-Z][A-Z\s]{10,})" # TITOLO IN MAIUSCOLO
r")"
)
splits = clause_pattern.split(text)
# Filtra clausole troppo corte (< 50 caratteri)
return [
clause.strip()
for clause in splits
if len(clause.strip()) > 50
]
# Esecuzione
result = analyze_contract("contratto_fornitura.pdf")
print(json.dumps(
{
"parties": result.parties,
"risk_summary": result.risk_summary,
"clause_count": len(result.clauses),
"obligation_count": len(result.obligations),
},
indent=2, ensure_ascii=False
))
契約AI向けLegalTechプラットフォーム
2026 年のリーガルテック市場では、いくつかのプラットフォームが AI ベースの契約分析ソリューションを提供します。 この分野の資金は次の水準に達しました 2025年には43億ドル、増加 2024 年と比較して 54% 増加します。主なプラットフォームとその技術的アプローチを見てみましょう。
| プラットフォーム | 専門分野 | AI技術 | 典型的なユーザー | 参考価格 |
|---|---|---|---|---|
| キラシステムズ (文学) | M&Aデューデリジェンス | ハイブリッド ML + 生成 AI | 上位 100 の法律事務所のうち 70 以上の法律事務所 | エンタープライズ (カスタム) |
| 輝度 | 異常検出、コンプライアンス | 独自の法的グレードの AI | 法律事務所、企業 | 月額500ドルから |
| 鉄壁の | 契約ライフサイクル管理 | AI Assistによるレビューと交渉 | 法務、調達 | 月額 $300 から |
| 魔導書 | 草案作成とレビューの支援 | GPT-4 + 法的微調整 | 弁護士、パラリーガル | 月額 400 ドルから |
| リーガルフライ | 多言語契約の見直し | LLM + 特化型 NER | 国際学 | 月額200ドルから |
| シリオン | コントラクト・インテリジェンス企業 | 抽出と分析のための AI | Fortune 500、調達 | エンタープライズ (カスタム) |
| ジョン・スノー研究所 | NLP パイプライン (600 以上のモデル) | Spark NLP + 法的 NLP | データサイエンスチーム | オープンソース + エンタープライズ |
Kira Systems: デューデリジェンスのリファレンス
キラシステムズ、現在は Litera の一部であり、上位 25 のスタジオの約 80% で使用されています。 M&Aを専門とする世界中の弁護士。独自の ML モデルを組み合わせたハイブリッド アプローチ 合成と分析のための生成機能を備えた 100 万件以上の契約に基づいてトレーニングを受けています。の Kira の強みは、1,000 種類を超える事前定義条項(組み込み条項)を抽出できることです。 また、特定の顧客条項に基づいてカスタム モデルを数時間でトレーニングできる可能性もあります。
輝度: 契約の異常検出
輝度 識別能力が際立っている 異常 契約書: 標準から逸脱した条項、異常な条項、条件の欠落。彼の 建築 法定レベルの AI、2026 年の初めに更新され、歴史を結びます。 ライフサイクル全体にわたる交渉、法的推論、ビジネス上の意思決定 契約の。特に、国境を越えたレビューで強力です。 管轄区域は隠れたリスクを生み出します。
評価指標とベストプラクティス
Contract AI システムを評価するには、パイプラインの各段階に特定の指標が必要です。システム 98% の OCR 精度を達成しますが、条項分類ではわずか 60% 以下です。 95% の OCR と 85% の分類を備えたものよりも有用です。
フェーズごとの指標
| 段階 | 主要な指標 | 許容可能なしきい値 | 優れた閾値 |
|---|---|---|---|
| OCR | 文字エラー率 (CER) | < 5% | < 1% |
| セグメンテーション | 境界線 F1 | > 80% | > 92% |
| NER | エンティティ F1 (厳密) | > 75% | > 88% |
| 約款の分類 | F1マクロ | > 78% | > 88% |
| 義務の検出 | デオンティッククラスのF1 | > 72% | > 85% |
| エンドツーエンド | タスク完了率 | > 70% | > 90% |
契約 AI プロジェクトのベスト プラクティス
10の黄金律
- トリアージから始めます: ネイティブ PDF には OCR を適用しないでください。契約の60~70% 現代のビジネスではすでにデジタル形式になっています。
- 前処理に投資します。 OCR の品質によって上限が決まります パイプライン全体のパフォーマンス。前処理に 1 時間投資すると、NER で 10 時間を節約できます。
- ドメイン固有のテンプレートを使用します。 LegalBERT は、一般的な BERT より 12 ~ 15% 優れています。 法的な任務。微調整にかかるコストは利益に比べればわずかです。
- ルールと ML を組み合わせる: 決定論的ルール (日付、金額、 規範的参照)は、構造化パターンの場合、ML よりも正確で説明可能です。
- エンドツーエンドの測定: セグメンテーションが間違っている場合は完璧ですが役に立たない NER です。 重要な指標は、 タスク完了率: エンドユーザーが使用する句の数 修正なしで受け入れます。
- 人間参加者: AIは弁護士に取って代わるのではなく、弁護士を加速させます。予測する 専門家が予測を確認、修正、改善するレビュー インターフェイスが常にあります。
- モデルのバージョン管理: 契約は進化します (GDPR や NIS 指令 2)。モデルは更新されたデータで定期的に再トレーニングする必要があります。
- 設計によるプライバシー: 契約には機密データ(金額、当事者、 商業条件)。オンプレミスで処理するか、クラウド プロバイダーからの契約上の保証を受けて処理します。
- 特定の使用例から始めます。 システムを構築しようとしないでください 普遍的な。契約の種類 (NDA、供給など) とタスク (日付抽出など) から開始します。 期限)を設定し、拡張する前に価値を実証します。
- データのドリフトを監視します。 契約上の言語は進化します。 AIに関する条項、 ESG、サプライチェーンのレジリエンスが一般的になったのはここ 2 ~ 3 年です。モデル パフォーマンスの低下を特定するには監視する必要があります。
業務システムとの統合パターン
契約 AI システムは、既存のビジネス プロセスに統合されて初めて価値を発揮します。出力 パイプラインの 契約ライフサイクル管理 (CLM), 文書管理システム (DMS) e 承認ワークフロー.
統合アーキテクチャ
- イベント駆動型: 新しい契約を DMS にロードすると、自動的に有効化されます Webhook またはメッセージ キュー (RabbitMQ、SQS) を介した分析パイプライン。
- REST API: ドキュメントを送信し、構造化された結果を受け取るエンドポイント JSON 形式で、あらゆる CLM と互換性があります。
- バッチ処理: 過去の契約アーカイブの移行については、 非同期処理と完了通知。
- ダッシュボード: 結果の表示とレビューのための Web インターフェイス モデルを継続的に改善するための予測とフィードバック。
標準と出力形式
相互運用性を最大限に高めるには、パイプライン出力はオープンスタンダードに従う必要があります。 OASIS LegalDocML (Akoma Ntoso) ドキュメンタリー構成、 SALI (法律業界の標準化推進) の分類のために 条項、e JSON-LD 構造化メタデータの場合。これらの規格により、 カスタム マッピングを使用しないサードパーティ システムとの統合。
結論と次のステップ
NLP による自動契約分析は、もはや学術研究プロジェクトではありません。 大手法律事務所や法務部門で日常的に使用されている成熟したテクノロジー 世界中の企業。この記事で構築したパイプライン (OCR から理解まで) セマンティクスは、すべての LegalTech プラットフォームの基礎となる基本的なアーキテクチャを表します。 現代的な。
覚えておくべき重要なポイント:
- パイプラインと 多段式: OCR → 構造化 → NER → 分類 → 意味分析。各フェーズには特定のテクノロジーとメトリクスがあります。
- I ドメイン固有のモデル (LegalBERT、CaseLaw-BERT) が大幅に優れたパフォーマンス 法的業務に関する一般的なモデル。 CUAD などのデータセットの微調整にもアクセスできます。 リソースが限られているチーム。
- アプローチ ハイブリッド ルール + ML そして最も効果的なのは、構造化されたエンティティのルールです。 (日付、金額、参照)、セマンティクスと分類のための ML。
- Il 人間関係者 それは妥協ではなく必然です: AI の加速 法的活動はそれに代わるものではありません。検証には人間による監視が不可欠 そして継続的な改善。
- L'統合 変革には CLM、DMS、ビジネス ワークフローの併用が不可欠 ビジネス価値を分析する技術的分析。
シリーズの次の記事では、 AIを活用した法学研究、 セマンティック検索エンジンとナレッジ グラフがどのように変革を遂げているかを確認する 弁護士と法学者は法学と教義にアクセスします。
詳細を学ぶためのリソース
- CUAD データセット: github.com/TheAtticusProject/cuad - 注釈付き契約書 510 件、条項タイプ 41 件
- LegalBERT: HuggingFace nlpaueb/legal-bert-base-uncased - 合法的な事前トレーニング済みモデル
- spaCy 法的 NER: github.com/openlegaldata/legal-ner - 法律文書用のオープンソース NER
- John Snow Labs の法的 NLP: 完全なエンタープライズ パイプライン用の 600 以上のテンプレート
- OASIS LegalDocML: 構造化された法的文書の XML 標準







