Skip to content

솔로 모드

Potato에서 주석 파이프라인 전체를 혼자 실행합니다. 전문가 한 명과 LLM이 단계 상태 머신을 따라 프롬프트 작성, 병렬 주석, 불일치 해소, 최종 검증까지 진행합니다.

v2.3.0의 새로운 기능

전통적인 주석 프로젝트에는 여러 명의 주석자, 주석자 간 일치도 계산, 판정 라운드, 그리고 상당한 조율 부담이 필요합니다. 많은 연구 팀에게 이것이 주요 병목입니다. 문제는 주석 인터페이스가 아니라 팀을 고용하고 훈련하고 관리하는 운영 부담입니다.

솔로 모드는 여러 주석자 패러다임을 LLM과 협업하는 인간 전문가 한 명으로 대체합니다. 인간은 전략적으로 선별된 작은 부분집합에 고품질 라벨을 제공합니다. LLM은 그 라벨로부터 학습해 나머지 데이터에 라벨을 제안하고, 인간은 LLM이 불확실하거나 틀렸을 가능성이 높은 경우만 검토합니다. 이 과정은 단계 상태 머신이 조율합니다. 상태 머신은 사용자가 설정한 임계값에 따라 다음 단계로 넘어가고, 프롬프트 자체가 문제로 드러나면 언제든 프롬프트로 되돌아갑니다.

내부 벤치마크에서 솔로 모드는 전체 인간 라벨의 10~15%만 사용하면서 여러 주석자 파이프라인 전체와 95% 이상의 일치도를 달성했습니다.

워크플로

솔로 모드는 상태 머신입니다. SoloPhaseController가 프로젝트의 현재 단계와 다음에 이동할 수 있는 단계를 추적합니다. 따라서 경로는 일직선이 아닙니다. 프롬프트에 아직 손볼 곳이 있으면 여러 단계가 앞 단계로 되돌아갈 수 있습니다.

설정

작업 설명과 데이터를 준비합니다. 이 단계에서 넘어갈 수 있는 곳은 프롬프트 검토뿐입니다.

프롬프트 검토

Potato가 작업 설명으로부터 프롬프트를 합성하고, 사용자가 이를 편집합니다. 프롬프트가 문제로 드러나면 이후의 여러 단계가 이곳으로 돌아옵니다. 그래서 이 단계는 한 번 거치고 끝나는 단계가 아니라 워크플로 전체의 중심입니다.

여기서 프로젝트는 경계 사례 루프를 거치거나 곧바로 병렬 주석으로 넘어갑니다.

경계 사례 합성, 그다음 경계 사례 라벨링

LLM이 경계 예시를 생성하고 사용자가 라벨을 붙입니다. 예시를 통해 프롬프트가 잘못되었음이 드러나면 라벨링 단계에서 프롬프트 검토로 돌아갈 수 있습니다.

프롬프트 검증

경계 사례에 붙인 사용자의 라벨을 프롬프트가 산출하는 결과와 대조합니다. 병렬 주석으로 진행하거나 프롬프트 검토로 돌아갑니다.

병렬 주석

사용자와 LLM이 같은 데이터를 동시에 주석합니다. 이 단계에서 나오는 일치도 수치를 이후의 모든 단계가 기준으로 삼습니다.

불일치 해소

사용자와 LLM의 라벨이 다른 곳은 사용자가 결론을 내립니다. 병렬 주석으로 돌아가거나, 불일치가 개별 항목이 아니라 프롬프트를 가리킬 때는 프롬프트 검토로 돌아갑니다.

능동 주석

주된 주석 단계로, instance_selection의 가중치 풀이 인스턴스를 고릅니다. 여기서 Potato는 주기적 검토, 규칙 검토, 또는 자율 라벨링으로 넘어갈 수 있습니다.

주기적 검토

신뢰도가 낮은 LLM 라벨을 thresholds.periodic_review_interval개 항목마다 보여 줍니다. 능동 주석으로 돌아가거나, 규칙 검토로 넘어가거나, 프롬프트 검토로 되돌아갑니다.

규칙 검토

Potato가 사용자의 라벨링에서 추출한 경계 사례 규칙을 승인받기 위해 제시합니다. 능동 주석이나 프롬프트 검토로 돌아갑니다.

자율 라벨링

LLM이 남은 데이터에 라벨을 붙입니다. 이 단계에서 나갈 수 있는 곳은 최종 검증뿐입니다.

최종 검증

사용자가 LLM만 라벨을 붙인 항목 중 일부 표본을 확인합니다. thresholds.end_human_annotation_agreement를 넘기면 프로젝트가 완료되고, 넘기지 못하면 능동 주석으로 되돌아갑니다.

완료

종료 상태입니다.


구성

빠른 시작

최소한의 솔로 모드 구성:

yaml
annotation_task_name: "Sentiment Classification"
task_dir: "."
 
data_files:
  - "data/reviews.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
solo_mode:
  enabled: true
 
  # The models that label. A list: the first is used, the rest are fallbacks.
  labeling_models:
    - endpoint_type: openai
      model: "gpt-4o"
      api_key: ${OPENAI_API_KEY}
      temperature: 0.1
      max_tokens: 1024
 
  # When the project may stop asking the human
  thresholds:
    end_human_annotation_agreement: 0.92
    minimum_validation_sample: 50
    confidence_low: 0.5
 
  state_dir: "solo_state_sentiment"
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment of this text?"
    labels:
      - Positive
      - Neutral
      - Negative
 
output_annotation_dir: "output/"
export_annotation_format: "jsonl"

전체 구성 레퍼런스

yaml
solo_mode:
  enabled: true
 
  # Models that label, and models that revise. Both are lists; revision_models
  # defaults to labeling_models when omitted.
  labeling_models:
    - endpoint_type: openai       # openai, anthropic, ollama, vllm, ...
      model: "gpt-4o"
      api_key: ${OPENAI_API_KEY}
      temperature: 0.1
      max_tokens: 1024
  revision_models:
    - endpoint_type: anthropic
      model: "claude-sonnet-4-20250514"
      api_key: ${ANTHROPIC_API_KEY}
      max_tokens: 1024
 
  # Sentence embeddings, used for diversity and neighbour lookups
  embedding:
    model_name: "all-MiniLM-L6-v2"
 
  # How the LLM's uncertainty is measured
  uncertainty:
    strategy: direct_confidence
    sampling_diversity:
      num_samples: 5
      temperature: 1.0
 
  # When the human can stop, and what counts as agreement per scheme type
  thresholds:
    end_human_annotation_agreement: 0.90
    minimum_validation_sample: 50
    confidence_low: 0.5
    confidence_high: 0.8
    periodic_review_interval: 100
    likert_tolerance: 1
    multiselect_jaccard_threshold: 0.5
    textbox_embedding_threshold: 0.7
    span_overlap_threshold: 0.5
 
  # Weights for the pools that choose what the human sees next
  instance_selection:
    low_confidence_weight: 0.3
    disagreement_weight: 0.1
    diversity_weight: 0.2
    llm_predicted_weight: 0.3
    random_weight: 0.1
    cartography_weight: 0.0
    edge_case_rule_weight: 0.0
 
  batches:
    llm_labeling_batch: 50
    max_parallel_labels: 200
 
  labeling_functions:
    enabled: true
    auto_extract: true
    max_functions: 50
    min_confidence: 0.85
    min_coverage: 3               # instances, not a fraction
    vote_threshold: 0.5
 
  edge_case_rules:
    enabled: true
    auto_extract_on_labeling: true
    confidence_threshold: 0.75
    min_rules_for_clustering: 10
    target_cluster_size: 15
    reannotation_enabled: true
    reannotation_confidence_threshold: 0.60
    max_reannotations_per_instance: 2
 
  confusion_analysis:
    enabled: true
    min_instances_for_pattern: 3
    max_patterns: 20
    auto_suggest_guidelines: false
 
  prompt_optimization:
    enabled: true
    find_smallest_model: true
    target_accuracy: 0.85
    optimization_interval_seconds: 300
    accuracy_weight: 0.7
    length_weight: 0.2
 
  # Send cheap cases to a small model and hard ones to a larger one
  confidence_routing:
    enabled: false
    tiers:
      - name: cheap
        confidence_threshold: 0.8
        model:
          endpoint_type: ollama
          model: "llama3.2"
 
  refinement_loop:
    enabled: true
    trigger_interval: 50
    min_improvement: 0.02
    max_cycles: 5
    patience: 2
 
  state_dir: "solo_state_sentiment"

주요 기능

혼동 분석

각 라벨링 라운드가 끝나면 Potato는 인간 라벨과 LLM 라벨 사이의 혼동 행렬을 구축합니다. 관리자 대시보드는 다음을 보여 줍니다.

  • LLM 관점에서의 클래스별 정밀도, 재현율, F1
  • 가장 흔한 혼동 쌍(예: "중립을 긍정으로 오분류: 23개 인스턴스")
  • 각 혼동 쌍에 대한 예시 인스턴스
  • 정제 라운드에 걸친 개선을 보여 주는 추세 차트

솔로 모드에는 명령줄 인터페이스가 없습니다. 솔로 모드는 서버 안에서 실행되고, 실행 중인 인스턴스 아래의 전용 페이지에서 조작합니다. 따라서 분석 결과는 CLI가 아니라 대시보드에서 확인합니다. 보고 내용은 다음과 같습니다.

text
Confusion Analysis (Round 2)
============================
Overall Accuracy: 0.87 (target: 0.92)

Top Confusion Pairs:
  neutral -> positive:  23 instances (15.3%)
  negative -> neutral:  11 instances (7.3%)
  positive -> neutral:   5 instances (3.3%)

Per-Class Performance:
  Positive:  P=0.91  R=0.94  F1=0.92
  Neutral:   P=0.78  R=0.71  F1=0.74
  Negative:  P=0.93  R=0.88  F1=0.90

자동화된 정제 루프

정제 루프는 LLM 라벨링, 혼동 분석, 지침 업데이트를 반복합니다. 각 반복은 다음과 같이 진행됩니다.

  1. LLM이 현재 지침으로 전체 데이터셋에 라벨을 붙입니다
  2. Potato가 사용 가능한 모든 인간 라벨과 비교합니다
  3. 정확도가 임계값 미만이면 혼동 분석이 실행됩니다
  4. LLM이 오류 패턴을 바탕으로 지침 편집을 제안합니다
  5. 인간이 편집을 검토하고 승인합니다
  6. 주기가 반복됩니다(max_cycles까지)
yaml
solo_mode:
  revision_models:
    - endpoint_type: anthropic
      model: "claude-sonnet-4-20250514"
      api_key: ${ANTHROPIC_API_KEY}
 
  refinement_loop:
    enabled: true
    trigger_interval: 50           # run a cycle every 50 new human labels
    min_improvement: 0.02          # stop if a cycle gains less than 2%
    max_cycles: 5
    patience: 2                    # cycles below min_improvement before stopping

라벨링 함수(ALCHEmist에서 영감)

Potato는 인간 주석에서 관찰된 패턴으로부터 가벼운 라벨링 함수를 생성합니다. 이것은 LLM 호출이 아니라 빠르고 결정론적인 규칙입니다.

생성된 라벨링 함수 예시:

python
# Auto-generated labeling function 1
# Precision: 0.96, Coverage: 0.08
def lf_strong_positive_words(text):
    positive = {"excellent", "amazing", "fantastic", "outstanding", "perfect"}
    if any(w in text.lower() for w in positive):
        if not any(neg in text.lower() for neg in {"not", "never", "no"}):
            return "Positive"
    return None  # abstain
 
# Auto-generated labeling function 2
# Precision: 0.93, Coverage: 0.05
def lf_explicit_negative(text):
    negative = {"terrible", "awful", "horrible", "worst", "disgusting"}
    if any(w in text.lower() for w in negative):
        return "Negative"
    return None

라벨링 함수 동작 구성:

yaml
solo_mode:
  labeling_functions:
    enabled: true
    auto_extract: true
    max_functions: 50
    min_confidence: 0.85
    min_coverage: 3          # minimum instances a function must cover
    vote_threshold: 0.5      # agreement needed when functions disagree

불일치 탐색기

불일치 탐색기는 서로 다른 신호가 충돌하는 인스턴스를 제시합니다. 각 인스턴스에 대해 주석자는 다음을 봅니다.

  • LLM이 예측한 라벨과 신뢰도
  • 라벨링 함수의 투표(있는 경우)
  • 임베딩 공간에서 가장 가까운 라벨링된 이웃
  • 원본 텍스트/콘텐츠

이것은 가장 가치가 높은 주석 활동입니다. 라벨 하나하나가 실제 모호성을 해소하기 때문입니다.

yaml
solo_mode:
  # Disagreement is a phase, not a configurable block. What you control is how
  # heavily disagreement weighs when Potato picks the next instance.
  instance_selection:
    disagreement_weight: 0.3
    low_confidence_weight: 0.3
    diversity_weight: 0.2
    llm_predicted_weight: 0.1
    random_weight: 0.1

단계적 신뢰도 에스컬레이션

데이터셋의 대부분을 LLM이 라벨링한 뒤, Potato는 LLM이 라벨링한 모든 인스턴스를 신뢰도 순으로 정렬하고 신뢰도가 가장 낮은 것을 인간에게 에스컬레이션합니다. 이 과정은 품질이 안정될 때까지 배치 단위로 계속됩니다.

yaml
solo_mode:
  thresholds:
    confidence_low: 0.5           # below this, the human sees it
    confidence_high: 0.8          # above this, the LLM's label stands
    periodic_review_interval: 100 # surface low-confidence labels this often
 
  batches:
    llm_labeling_batch: 50
    max_parallel_labels: 200

다중 신호 인스턴스 우선순위 지정

인간 라벨링이 포함된 모든 단계에서 Potato는 가중치 풀 시스템으로 가장 유익한 인스턴스를 선택합니다. 일곱 개의 풀이 하나의 우선순위 큐로 들어갑니다.

yaml
solo_mode:
  instance_selection:
    low_confidence_weight: 0.3
    diversity_weight: 0.2
    llm_predicted_weight: 0.3
    disagreement_weight: 0.1
    random_weight: 0.1
    cartography_weight: 0.0
    edge_case_rule_weight: 0.0
  • low_confidence: LLM의 신뢰도가 thresholds.confidence_low 미만인 경우
  • diversity: 임베딩 공간에서 이미 라벨링한 항목과 멀리 떨어진 경우
  • llm_predicted: LLM이 붙인 라벨이 있지만 아직 확인되지 않은 경우
  • disagreement: 해당 인스턴스에 대해 사용 가능한 신호들이 서로 일치하지 않는 경우
  • random: 추정을 정직하게 유지하고 사각지대를 포착하기 위한 작은 무작위 추출
  • cartography: 학습 동역학 신호, 기본값은 꺼짐
  • edge_case_rule: 이전 라벨링에서 추출한 규칙과 일치하는 경우, 기본값은 꺼짐

경계 사례 합성

Potato는 LLM을 사용해 알려진 약점을 겨냥하는 합성 예시를 생성합니다.

yaml
solo_mode:
  edge_case_rules:
    enabled: true
    auto_extract_on_labeling: true   # derive rules as you label
    confidence_threshold: 0.75
    min_rules_for_clustering: 10
    target_cluster_size: 15
    reannotation_enabled: true       # revisit items a new rule contradicts
    reannotation_confidence_threshold: 0.60
    max_reannotations_per_instance: 2

LLM은 지정된 라벨 쌍 사이에서 모호한 예시를 생성합니다. 인간이 이를 라벨링하고, 이 라벨은 이후 LLM 라벨링 라운드의 few-shot 컨텍스트에 추가됩니다.

프롬프트 최적화(DSPy에서 영감)

Potato는 LLM에 가장 적합한 지시 형식을 찾기 위해 자동화된 프롬프트 최적화를 실행하고, 목표 정확도를 유지하는 가장 작은 모델도 찾습니다.

yaml
solo_mode:
  prompt_optimization:
    enabled: true
    find_smallest_model: true          # prefer the cheapest model that holds up
    target_accuracy: 0.85
    optimization_interval_seconds: 300
    accuracy_weight: 0.7               # how the candidates are scored
    length_weight: 0.2

진행 상황 모니터링

관리자 대시보드는 솔로 모드 진행 상황을 실시간으로 보여 줍니다.

  • 현재 단계와 각 단계 내의 진행 상황
  • 완료된 인간 라벨 대 총 예산
  • 시간 경과에 따른 LLM 정확도(라운드별)
  • 라벨링 함수의 커버리지와 정밀도
  • 신뢰도 분포 히스토그램
  • 완료까지의 예상 시간

potato.solo 모듈도, 솔로 모드 CLI도 없습니다. 워크플로 전체가 서버 자체의 라우트 뒤에 있으므로 진행 상황은 대시보드에서 확인합니다. 대시보드는 다음을 보고합니다.

text
Solo Mode Status
================
Current Phase: 6 (Active Labeling) - Batch 3/10
Human Labels: 142 / ~300 estimated total
LLM Accuracy: 0.89 (target: 0.92)
LF Coverage: 0.23 (labeling functions cover 23% of data)
Dataset Size: 10,000 instances
  - Human labeled: 142
  - LF labeled: 2,300
  - LLM labeled: 7,558
  - Unlabeled: 0

솔로 모드와 전통적인 여러 주석자 방식, 언제 무엇을 쓸까

다음과 같을 때 솔로 모드를 사용하세요:

  • 고품질 라벨을 제공할 수 있는 도메인 전문가가 있습니다
  • 예산이나 운영 사정으로 여러 주석자를 고용할 수 없습니다
  • 작업의 범주가 명확하고 잘 정의되어 있습니다
  • 큰 데이터셋(1,000개 이상의 인스턴스)에 라벨을 붙여야 합니다
  • 주석자 간 일치도를 측정하는 것보다 속도가 더 중요합니다

다음과 같을 때 전통적인 여러 주석자 방식을 사용하세요:

  • 논문 발표를 위해 주석자 간 일치도 통계가 필요합니다
  • 작업이 매우 주관적입니다(예: 불쾌감, 유머)
  • 주석자 불일치 패턴을 연구해야 합니다
  • 규제 요건상 여러 명의 독립적인 주석자가 필요합니다
  • 라벨 공간이 복잡하거나 계속 바뀌고 있습니다(주석 지침이 아직 개발 중입니다)

하이브리드 접근: 초기 대량 라벨링에는 솔로 모드를 사용하고, 그다음 무작위 10~20% 표본에 두 번째 주석자를 배정해 일치도 통계를 계산하세요. 이렇게 하면 솔로 모드의 효율성과 여러 주석자 검증의 품질 보증을 함께 얻을 수 있습니다.

솔로 모드에는 자체 검증 블록이 없습니다. 두 번째 주석자는 내보낸 라벨을 대상으로 일반 Potato 작업으로 실행하고, 그 주석자의 라벨과 솔로 모드의 라벨 사이의 일치도는 여느 두 주석자에게 하듯이 계산하세요. (icl_labeling에는 verification 블록이 있지만, 이는 솔로 모드가 아니라 인컨텍스트 라벨링에 속합니다.)


더 읽어보기

구현 세부 사항은 원본 문서를 참조하세요.