AIチームにあるトレーニング文書の出所を尋ねると、返ってくるのはデータセット名であって情報源ではないことが多い。規制当局、顧客、裁判所が今求めているのは次の答えだ。どのページを、いつ取得し、どの条件のもとで、トレーニング前にどんな処理を施したのか。ウェブデータからモデルを構築するチームにとって、provenance(来歴)はもはや望ましい慣行ではなく、示せなければならないものになった。
難しいのはタイミングだ。provenanceの記録に必要な情報のほとんどは、収集した瞬間にしか存在しない。本ガイドでは、この問題がなぜ急務になったのか、すべての文書について保持すべき記録とは何か、そして抽出・フィルタリング・重複排除を経てもその記録をどう引き継ぐかを扱う。
provenanceが要件となった理由
3つの動きが、provenanceを研究課題からコンプライアンス業務へと変えた。
EU AI Actが公開要約を求めている。 第53条1項(d)は、汎用AIモデルの提供者に対し、AI Officeが用意するテンプレートを用いて「トレーニングに使用されたコンテンツに関する十分に詳細な要約」を公開することを義務付けている。この義務は2025年8月2日から適用され、それ以前に既に市場に出ているモデルについては2027年8月2日までの猶予がある。欧州委員会は2025年7月にこのテンプレートを提示し、その使用は義務であると明言している。
テンプレートは具体性を求めている。 スクレイピングしたデータについては、使用したクローラーの名称、収集期間、そして最も関連性の高いドメイン名のリスト、すなわちスクレイピングされたコンテンツ量の上位10%にあたるドメイン(中小企業の場合は上位5%または1,000ドメインのいずれか低い方)を求めている。また、テキスト・データマイニングのオプトアウトを「データ収集の前および収集中に」どう尊重したかを説明するよう求めている。第53条1項(c)は別途、こうした留保を特定し遵守する著作権ポリシーを義務付けている。
コモンズは縮小しており、記録の質は低かった。 Data Provenance Initiativeは2023年に1,800以上のテキストデータセットを監査し、広く使われているホスティングサイトでライセンス表記の欠落率が70%超、誤り率が50%超であることを発見した。これに続く14,000のウェブドメインを対象とした監査では、わずか1年のうちにC4の全トークンの約5%、そして最も活発に維持されている情報源の28%が、robots.txtによって完全に制限されるようになっていたことが判明した。クロールした時点では許容されていたデータセットが、今日クロールするには許容されないかもしれない。そしてその違いを示せるのは、当時の状態を記録していた場合に限られる。
すべての文書について記録すべきこと
provenanceの記録は、取得時にクローラーによって一度だけ書かれる。その後の工程はすべて、それを置き換えるのではなく、そこに追記していく。
| フィールド | 重要な理由 |
|---|---|
| ソースURL | ドメインリストと削除要請の基礎となる |
| 取得日時(UTC) | 収集期間とその日時点でのオプトアウト状態を証明する |
| レスポンスステータス | 実際のコンテンツをブロックページやエラーと区別する |
| 生の本文のハッシュ | 後のすべてのコピーを、実際に受信したものと紐付ける |
| 観測地点 | 同じURLでも国やネットワークによって異なるコンテンツを返す場合がある |
| robots.txtの判定 | 取得時点で有効だった規則をクローラーが遵守したことを示す |
| 確認されたTDM留保 | EUの著作権規則とテンプレートが注目するオプトアウト |
| 明示されたライセンスまたは利用規約 | ページに再利用について何が書かれていたか |
| クローラー名とバージョン | テンプレートはクローラー名を求める。バージョンは挙動の変化を説明する |
| 親レコード | 整形または重複排除後のコピーを元のソースに結び付ける |
見落とされがちなフィールドが2つある。1つ目は観測地点だ。あるページを1つの国やネットワークから観測した場合と、別の場所から同じURLを見た場合とでは内容が異なることがある。これはウェブデータの観測地点を記録するという記事の論点でもある。2つ目は生の本文そのものだ。ハッシュはどのバイト列を受信したかを証明するが、レスポンスそのものを保持すること、例えばWARCファイルでページをアーカイブすることで、それを実際に示すことができる。
コードで見るprovenanceレコード
以下の例は、取得した文書ごとに1件のレコードを書き込み、取得時点で許可されかつ留保されていなかった文書のみを保持し、抽出された各コピーをハッシュによって元のソースに結び付ける。また、要約テンプレートが求めるドメインのランキングも生成する。Python 3.9以降で、標準ライブラリのみで動作する。
from __future__ import annotations
import hashlib
import json
from collections import Counter
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from urllib.parse import urlsplit
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
@dataclass
class Provenance:
"""One record per fetched document, written at collection time."""
url: str
fetched_at: str # ISO 8601, UTC
status: int
content_sha256: str # hash of the raw response body
vantage: str # where it was observed from, e.g. "residential:de"
robots_allowed: bool # robots.txt decision for our crawler, at fetch time
tdm_reserved: bool # machine-readable TDM opt-out seen, at fetch time
license: str | None # licence or terms as stated on the page, if any
collector: str # crawler name and version
parents: list[str] = field(default_factory=list) # hashes this record derives from
step: str = "fetch" # fetch, extract, filter, dedup ...
def fetched(url, body, status, vantage, robots_allowed, tdm_reserved, license=None,
collector="corpus-crawler/1.4"):
return Provenance(
url=url,
fetched_at=datetime.now(timezone.utc).isoformat(timespec="seconds"),
status=status,
content_sha256=sha256(body),
vantage=vantage,
robots_allowed=robots_allowed,
tdm_reserved=tdm_reserved,
license=license,
collector=collector,
)
def derived(parent: Provenance, new_body: bytes, step: str) -> Provenance:
"""A transformed copy keeps the source fields and points back at its parent."""
rec = Provenance(**{**asdict(parent), "parents": [parent.content_sha256], "step": step})
rec.content_sha256 = sha256(new_body)
return rec
def eligible(rec: Provenance) -> bool:
"""Only documents that were allowed and not reserved at fetch time go further."""
return rec.status == 200 and rec.robots_allowed and not rec.tdm_reserved
def domain_summary(records, top_share=0.1):
"""Domains by document count, and the top share of them, for a training-data summary."""
counts = Counter(urlsplit(r.url).hostname for r in records)
ranked = [d for d, _ in counts.most_common()]
keep = max(1, round(len(ranked) * top_share))
return counts, ranked[:keep]
if __name__ == "__main__":
raw = [
fetched("https://news.example/a", b"<html>a</html>", 200, "residential:de", True, False),
fetched("https://news.example/b", b"<html>b</html>", 200, "residential:de", True, False),
fetched("https://shop.example/p/1", b"<html>p1</html>", 200, "residential:fr", True, True),
fetched("https://blog.example/x", b"<html>x</html>", 403, "residential:us", True, False),
fetched("https://forum.example/t/9", b"<html>t9</html>", 200, "residential:us", False, False),
]
kept = [r for r in raw if eligible(r)]
texts = [derived(r, r.url.encode() + b" text", "extract") for r in kept]
with open("manifest.jsonl", "w") as f:
for rec in raw + texts:
f.write(json.dumps(asdict(rec)) + "\n")
counts, top = domain_summary(raw)
print(f"fetched {len(raw)}, eligible {len(kept)}, extracted {len(texts)}")
print("domains:", dict(counts))
print("top domains:", top)
child = texts[0]
print("lineage ok:", child.parents == [kept[0].content_sha256], child.step, child.url)
Output:
fetched 5, eligible 2, extracted 2
domains: {'news.example': 2, 'shop.example': 1, 'blog.example': 1, 'forum.example': 1}
top domains: ['news.example']
lineage ok: True extract https://news.example/a
留保されたページ、ブロックされたページ、robots.txtによって許可されなかったページも、すべてマニフェストに残っている。これが重要な点だ。使わなかったものの記録自体が、証拠の一部をなす。テンプレートはスクレイピングされたコンテンツ量でドメインを順位付けするため、本番環境では文書数ではなくバイト数やトークン数を数えること。
パイプライン全体を通じてprovenanceを引き継ぐ
取得時に書かれたレコードは、以降のすべての工程がその紐付けを保持して初めて役に立つ。
- 抽出。 トレーニングに使うテキストは、取得したHTMLそのものではない。抽出されたテキストには独自のハッシュを与え、上記の
derived()が行うように、生のレコードを指し示すようにする。 - フィルタリング。 文書が除外された事実だけでなく、その理由もログに残す。「品質閾値未満」と「TDM留保」は、規制当局にとって異なる答えだ。
- 重複排除。 ほぼ重複する文書を統合する際は、残ったコピーが代表するすべてのソースの一覧を保持する。そうしなければ、あるURLに対する削除要請が、それが最終的にどの文書に含まれたのかを見つけられなくなる。
- 混合とサンプリング。 各トレーニング実行がどのマニフェストのバージョンを使用したかを記録し、要約がモデルが実際に見たデータを記述するようにする。
- 削除。 URLとハッシュをキーとする削除リストを保持する。あるサイトが後日権利を留保した場合や、権利者から削除要請があった場合、同じキーを使ってすべての派生データセットからその文書を見つけられる。
オプトアウトの状態には特に注意が必要だ。robots.txtファイルやテキスト・データマイニングの留保は、クロール後にどちらの方向にも変化しうる。取得時点で下した判断を記録し、新たな収集を行う前には再確認すること。昨年の答えが今も有効だと仮定してはならない。
参考にすべき標準
スキーマをゼロから考案する必要はない。
- Datasheets for Datasets(Gebru他、Communications of the ACM、2021年)は、すべてのデータセットがその動機、構成、収集プロセス、推奨される用途を文書化することを提案している。データセット単位の要約にふさわしい形式だ。
- Data & Trusted AI AllianceによるData Provenance Standardsは、ソース、provenance、用途の3グループに分かれた22のメタデータフィールドを定義しており、データ生成の期間、方法、使用ライセンス、同意が文書化されている場所などを含む。バージョン1.0.0は2024年7月にリリースされた。
- C2PA Content Credentialsは、署名付きのprovenanceマニフェストを個々のメディア資産に付加する。画像、音声、動画において、資産自体が記録を持ち運べる点で最も有用だ。
provenanceが破綻しやすい箇所
- 後から追加される。 文書単位の記録を欠いたクロールは、データセットカードがどれほど優れていても、後から監査できない。
- ライセンスがデータセットカードから単純にコピーされる。 前述の監査で、ここに誤りが集中していることが判明した。ソースが実際に何と述べていたかを記録し、集約されたラベルは確認すべき主張として扱うこと。
- ブロックページがコンテンツとして数えられる。 ステータス200のレスポンスでも、チャレンジページであることがある。ステータスコードとコンテンツの確認は、記録に含めるべきものだ。
- 観測地点が不明。 各市場をローカルのレジデンシャル接続から収集することは、記録に各取得がどの国とネットワークを使用したかが書かれている場合にのみ、provenanceの役に立つ。
結論
provenanceは収集時に書くのは安価だが、後から復元するのはほぼ不可能だ。取得時に文書ごとに1件のレコードを書き、すべての変換を通じてハッシュを引き継ぎ、採用したものだけでなく除外したものも保持する。そうすれば、AI Actが求める要約は、調査ではなく単なる問い合わせになる。
出典および参考資料
- Regulation (EU) 2024/1689 (AI Act), Articles 53, 111 and 113: eur-lex.europa.eu
- European Commission, explanatory notice and template for the public summary of training content (July 2025): digital-strategy.ec.europa.eu
- Directive (EU) 2019/790, Article 4(3): eur-lex.europa.eu
- Longpre et al., The Data Provenance Initiative (2023): arxiv.org/abs/2310.16787
- Longpre et al., Consent in Crisis: The Rapid Decline of the AI Data Commons (2024): arxiv.org/abs/2407.14933
- Gebru et al., Datasheets for Datasets: doi.org/10.1145/3458723
- Data & Trusted AI Alliance, Data Provenance Standards v1.0.0: dtaalliance.org
- C2PA Specification: spec.c2pa.org