2026년 10월 7일 기준으로 Shifter는 Oxylabs와 Bright Data보다 많은, 가장 큰 규모의 실시간 미국 IP 풀을 보유하고 있으며, 비용은 그 일부에 불과합니다.Shifter는 이제 가장 많은 미국 라이브 IP 풀을 보유하고 있습니다

벤치마크 보기

AI 학습 파이프라인에서의 출처 증명: 데이터셋이 어디서 왔는지 입증하기

AI 학습 데이터 출처 증명이 실무에서 의미하는 것: 모든 문서에 대해 유지해야 할 기록, EU AI Act 요약본이 요구하는 사항, 그리고 이를 끝까지 이행하는 방법.

데이터 사이언스 · 2026년 10월 24일에 업데이트됨 · 8분 읽기
가져오기추출중복제거학습sha256 · url · fetched_at제외됨: 2
각 학습 사본은 해당 수집본을 가리킵니다. 호박색 기록은 제외된 상태로 남았으며, 계속 기록됩니다.
지식
이 페이지에서
  1. 출처 증명이 요구 사항이 된 이유
  2. 모든 문서에 대해 기록해야 할 것
  3. 코드로 작성한 출처 기록
  4. 파이프라인 전체에 출처 증명을 이어가기
  5. 참고할 만한 표준
  6. 출처 증명이 흔히 무너지는 지점
  7. 결론
  8. 출처 및 참고 자료

AI 팀에게 특정 훈련 문서가 어디서 왔는지 물어보면, 돌아오는 답은 대개 출처가 아니라 데이터셋 이름이다. 규제 당국, 고객, 법원은 이제 그 다음 답을 원한다. 어느 페이지를, 언제 가져왔고, 어떤 조건 하에서, 훈련 전에 어떤 처리를 거쳤는지에 관한 것이다. 웹 데이터로 모델을 구축하는 팀에게 출처 증명(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에 의해 완전히 제한되었다는 사실이 드러났다. 크롤링했을 당시에는 허용되던 데이터셋이 오늘 크롤링하기에는 허용되지 않을 수 있으며, 당시 상태를 기록해 두었을 때만 그 차이를 입증할 수 있다.

모든 문서에 대해 기록해야 할 것

출처 기록은 크롤러에 의해, 수집 시점에, 단 한 번 작성된다. 이후 모든 단계는 그것을 대체하는 것이 아니라 거기에 더해간다.

필드중요한 이유
출처 URL도메인 목록 및 삭제 요청의 근거
수집 시각 (UTC)수집 기간과 해당 날짜의 옵트아웃 상태를 증명
응답 상태실제 콘텐츠와 차단 페이지, 오류를 구분
원본 본문의 해시이후의 모든 사본을 수신한 정확한 내용과 연결
관측 지점(vantage point)동일한 URL이 국가와 네트워크에 따라 다른 콘텐츠를 제공할 수 있음
robots.txt 판단크롤러가 수집 시점에 유효한 규칙을 준수했음을 보여줌
확인된 TDM 유보EU 저작권 규정과 템플릿이 중요시하는 옵트아웃
명시된 라이선스 또는 이용 약관해당 페이지가 재사용에 관해 명시한 내용, 있다면
크롤러 이름 및 버전템플릿은 크롤러 이름을 요구하며, 버전은 동작 변화를 설명함
상위(parent) 기록정제되거나 중복 제거된 사본을 원본과 연결

두 가지 필드는 자주 누락된다. 첫 번째는 관측 지점이다. 한 국가나 네트워크에서 관측된 페이지는 다른 곳에서 본 동일한 URL과 다를 수 있으며, 이는 웹 데이터가 관측된 위치를 기록하는 것에 관한 우리 글의 논지다. 두 번째는 원본 본문 자체다. 해시는 수신한 바이트를 증명하지만, 예를 들어 페이지를 WARC 파일로 아카이빙하여 응답을 보관해 두면 이를 보여줄 수 있다.

코드로 작성한 출처 기록

아래 예제는 가져온 문서마다 하나의 기록을 작성하고, 수집 시점에 허용되었으며 유보되지 않은 문서만 유지하며, 추출된 각 사본을 해시로 원본과 연결한다. 또한 요약 템플릿이 요구하는 도메인 순위도 생성한다. 이 코드는 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)

출력:

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로 금지된 페이지 모두 매니페스트에 그대로 남아 있다. 이는 중요하다. 사용하지 않기로 선택한 것에 대한 기록도 증명의 일부이기 때문이다. 템플릿은 스크래핑된 콘텐츠의 크기로 도메인 순위를 매기므로, 실제 운영 환경에서는 문서 수가 아니라 바이트나 토큰 수를 세어야 한다.

파이프라인 전체에 출처 증명을 이어가기

수집 시점에 작성된 기록은 이후 모든 단계가 그 연결을 유지할 때만 유용하다.

  • 추출. 훈련에 사용하는 텍스트는 가져온 HTML이 아니다. 위의 derived()가 하듯, 추출된 텍스트에 자체 해시를 부여하고 원본 기록을 가리키게 하라.
  • 필터링. 문서가 제외되었다는 사실뿐 아니라 왜 제외되었는지도 기록하라. “품질 기준 미달”과 “TDM 유보”는 규제 당국에게는 다른 답변이다.
  • 중복 제거. 유사 중복 문서를 병합할 때는 살아남은 사본이 대표하는 모든 출처의 목록을 유지하라. 그렇지 않으면 특정 URL에 대한 삭제 요청이 그것이 결국 포함된 문서를 찾지 못한다.
  • 혼합 및 샘플링. 각 훈련 실행이 사용한 매니페스트 버전을 기록하여, 요약이 모델이 실제로 본 데이터를 설명하도록 하라.
  • 삭제. URL과 해시를 키로 하는 삭제 목록을 유지하라. 이후 어떤 사이트가 권리를 유보하거나 권리자가 삭제를 요청하면, 동일한 키로 파생된 모든 데이터셋에서 해당 문서를 찾을 수 있다.

옵트아웃 상태는 특히 주의가 필요하다. robots.txt 파일이나 텍스트 및 데이터 마이닝 유보는 크롤링 이후 어느 방향으로든 바뀔 수 있다. 수집 시점에 내린 판단을 기록하고, 작년의 답이 여전히 유효하다고 가정하기보다는 새로운 수집 전에 다시 확인하라.

참고할 만한 표준

처음부터 스키마를 새로 만들 필요는 없다.

  • Datasheets for Datasets (Gebru et al., Communications of the ACM, 2021)는 모든 데이터셋이 그 동기, 구성, 수집 과정, 권장 용도를 문서화해야 한다고 제안한다. 데이터셋 수준 요약에 적합한 형식이다.
  • Data & Trusted AI Alliance의 Data Provenance Standards는 세 그룹(출처, 출처 증명, 용도)으로 나뉜 22개의 메타데이터 필드를 정의하며, 여기에는 데이터 생성 기간, 방법, 사용 라이선스, 동의가 문서화된 위치가 포함된다. 버전 1.0.0은 2024년 7월에 발표되었다.
  • C2PA Content Credentials는 서명된 출처 증명 매니페스트를 개별 미디어 자산에 첨부한다. 이는 자산 자체가 기록을 담을 수 있는 이미지, 오디오, 비디오에 가장 유용하다.

출처 증명이 흔히 무너지는 지점

  • 사후에 추가된다. 문서별 기록이 없는 크롤링은 아무리 데이터셋 카드가 훌륭해도 나중에 감사할 수 없다.
  • 라이선스를 데이터셋 카드에서 그대로 복사한다. 위의 감사는 바로 이 지점에서 오류가 집중된다는 것을 발견했다. 출처가 실제로 명시한 내용을 기록하고, 집계된 라벨은 검증이 필요한 주장으로 취급하라.
  • 차단 페이지를 콘텐츠로 센다. 200 응답이라도 챌린지 페이지일 수 있다. 상태 코드와 콘텐츠 검사는 기록에 포함되어야 한다.
  • 관측 지점을 알 수 없다. 각 시장을 로컬 레지덴셜 연결로 수집하는 것은, 각 수집이 어느 국가와 네트워크를 사용했는지 기록에 남아 있을 때만 출처 증명에 도움이 된다.

결론

출처 증명은 수집 시점에 작성하면 비용이 거의 들지 않지만, 나중에 복구하는 것은 거의 불가능에 가깝다. 문서마다 수집 시점에 하나의 기록을 작성하고, 모든 변환 과정에서 해시를 유지하며, 제외한 것과 포함한 것을 모두 기록하라. 그러면 AI Act가 요구하는 요약은 조사가 아니라 질의가 된다.

출처 및 참고 자료

자주 묻는 질문

AI 학습 데이터의 데이터 출처(data provenance)란 무엇인가?

각 학습 데이터가 어디에서 왔으며 어떤 과정을 거쳤는지에 대한 기록이다. 즉 출처 URL이나 데이터셋, 언제 어디에서 수집되었는지, 그 시점의 옵트아웃 및 라이선스 상태, 그리고 학습 데이터로 들어오기까지 거친 모든 변환 단계를 말한다.

EU AI Act는 URL별 출처 기록을 요구하는가?

아니다. 범용 AI 모델 제공자는 집행위원회의 템플릿을 사용하여 학습 콘텐츠 요약본을 발행해야 하며, 여기에는 주요 데이터셋, 가장 관련성 높은 크롤링 대상 도메인, 크롤러 이름, 수집 기간, 옵트아웃이 어떻게 준수되었는지가 포함된다. 개별 URL을 나열하는 것은 선택 사항이지만, 문서 단위 기록 없이는 요구되는 요약본을 신뢰성 있게 작성할 수 없다.

데이터셋이 구축된 후에 출처를 재구성할 수 있는가?

부분적으로만 가능하다. 출처 URL은 때때로 복구할 수 있지만, 옵트아웃 상태, 실제로 수신된 응답, 가져온 시점의 관측 지점은 그 순간이 지나면 사라진다. 그렇기 때문에 출처 기록은 나중에 추가하는 것이 아니라 크롤러가 직접 작성해야 한다.

문서별 최소 출처 기록 항목은 무엇인가?

출처 URL, UTC 기준 수집 시각, 응답 상태, 원본 본문의 해시값, 관측된 관측 지점, 수집 시점의 robots.txt 및 텍스트 및 데이터 마이닝 옵트아웃 결정, 명시된 라이선스가 있다면 그 내용, 크롤러 이름과 버전, 그리고 파생된 원본 기록으로의 링크이다.

시작할 준비가 되셨나요?

205M개 이상의 IP, 195개 이상의 국가를 지원하는 Shifter의 레지덴셜 프록시를 $0.10/GB부터 이용해보세요.

시작하기