Skip to content
Guides8 min read

Label Studio에서 Potato로 마이그레이션하기

이 단계별 가이드로 Label Studio에서 Potato로 마이그레이션하면서 프로젝트 설정, 주석 스키마, 내보낸 데이터를 변환하고 일반적인 주석 유형을 모두 다룹니다.

Potato Team

이 가이드는 기존 Label Studio 프로젝트를 Potato로 옮기는 과정을 차근차근 설명합니다. 먼저 일러둘 것이 있습니다. 공식 마이그레이션 도구는 없습니다. 설정은 직접 손으로 변환하고 데이터를 재구성하려면 약간의 파이썬 코드를 작성해야 하므로, 두 플랫폼 모두에 익숙해야 합니다.

기능을 나란히 비교한 내용과 Potato 자체 마이그레이션 도구는 원본 문서마이그레이션 CLI 문서를 참고하십시오.

왜 마이그레이션해야 할까요?

어떤 프로젝트에는 Potato가 더 잘 맞습니다. Potato는 학술 주석 연구를 위해 만들어졌고, Prolific 및 MTurk 연동을 기본으로 제공하며, 띄울 데이터베이스 없이 YAML로 설정합니다. 파이썬으로 확장하기 쉽고, 저장소가 그냥 파일이기 때문에 배포하기도 쉽습니다.

마이그레이션 개요

이 과정은 수동으로 진행되며 대략 다음과 같이 흘러갑니다.

  1. Label Studio XML 템플릿을 Potato YAML 설정으로 직접 변환합니다
  2. 데이터 형식을 변환하는 파이썬 스크립트를 작성합니다(JSON에서 JSONL로)
  3. 기존 주석을 마이그레이션하는 스크립트를 작성합니다(있는 경우)
  4. 충분히 테스트하고 변환된 데이터를 검증합니다

템플릿 변환

텍스트 분류

Label Studio XML:

xml
<View>
  <Text name="text" value="$text"/>
  <Choices name="sentiment" toName="text" choice="single">
    <Choice value="Positive"/>
    <Choice value="Negative"/>
    <Choice value="Neutral"/>
  </Choices>
</View>

Potato YAML:

yaml
annotation_task_name: "Sentiment Classification"
 
data_files:
  - "data/items.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment?"
    labels:
      - name: positive
        tooltip: "Positive sentiment"
      - name: negative
        tooltip: "Negative sentiment"
      - name: neutral
        tooltip: "Neutral sentiment"

다중 레이블 분류

Label Studio XML:

xml
<View>
  <Text name="text" value="$text"/>
  <Choices name="topics" toName="text" choice="multiple">
    <Choice value="Politics"/>
    <Choice value="Sports"/>
    <Choice value="Technology"/>
    <Choice value="Entertainment"/>
  </Choices>
</View>

Potato YAML:

yaml
annotation_schemes:
  - annotation_type: multiselect
    name: topics
    description: "Select all relevant topics"
    labels:
      - name: politics
        tooltip: "Politics content"
      - name: sports
        tooltip: "Sports content"
      - name: technology
        tooltip: "Technology content"
      - name: entertainment
        tooltip: "Entertainment content"

개체명 인식

Label Studio XML:

xml
<View>
  <Labels name="entities" toName="text">
    <Label value="PERSON" background="#FFC0CB"/>
    <Label value="ORG" background="#90EE90"/>
    <Label value="LOCATION" background="#ADD8E6"/>
  </Labels>
  <Text name="text" value="$text"/>
</View>

Potato YAML:

yaml
annotation_schemes:
  - annotation_type: span
    name: entities
    description: "Select entity spans in the text"
    labels:
      - name: PERSON
        tooltip: "Person names"
      - name: ORG
        tooltip: "Organization names"
      - name: LOCATION
        tooltip: "Location names"

참고: Potato의 스팬 주석은 Label Studio와 다른 방식으로 강조 표시할 수 있습니다. 변환한 설정을 테스트하여 표시 방식이 요구 사항에 맞는지 확인하십시오.

이미지 분류

Label Studio XML:

xml
<View>
  <Image name="image" value="$image_url"/>
  <Choices name="category" toName="image">
    <Choice value="Cat"/>
    <Choice value="Dog"/>
    <Choice value="Other"/>
  </Choices>
</View>

Potato YAML:

yaml
data_files:
  - "data/images.jsonl"
 
item_properties:
  id_key: id
  text_key: image_url
 
annotation_schemes:
  - annotation_type: radio
    name: category
    description: "What animal is in the image?"
    labels:
      - name: cat
        tooltip: "Cat"
      - name: dog
        tooltip: "Dog"
      - name: other
        tooltip: "Other animal"

바운딩 박스 주석

Label Studio XML:

xml
<View>
  <Image name="image" value="$image_url"/>
  <RectangleLabels name="objects" toName="image">
    <Label value="Car"/>
    <Label value="Person"/>
    <Label value="Bicycle"/>
  </RectangleLabels>
</View>

Potato YAML:

yaml
annotation_schemes:
  - annotation_type: bounding_box
    name: objects
    description: "Draw boxes around objects"
    labels:
      - name: car
        tooltip: "Car"
      - name: person
        tooltip: "Person"
      - name: bicycle
        tooltip: "Bicycle"

참고: Potato의 바운딩 박스 지원은 Label Studio와 다를 수 있습니다. 현재 지원되는 기능은 문서를 확인하십시오.

평가 척도

Label Studio XML:

xml
<View>
  <Text name="text" value="$text"/>
  <Rating name="quality" toName="text" maxRating="5"/>
</View>

Potato YAML:

yaml
annotation_schemes:
  - annotation_type: likert
    name: quality
    description: "Rate the quality"
    size: 5
    labels:
      - name: "1"
        tooltip: "Poor"
      - name: "2"
        tooltip: "Below average"
      - name: "3"
        tooltip: "Average"
      - name: "4"
        tooltip: "Good"
      - name: "5"
        tooltip: "Excellent"

데이터 형식 변환

Label Studio JSON에서 Potato JSONL로

Label Studio 형식:

json
[
  {
    "id": 1,
    "data": {
      "text": "This is great!",
      "meta_info": "source1"
    }
  },
  {
    "id": 2,
    "data": {
      "text": "This is terrible.",
      "meta_info": "source2"
    }
  }
]

Potato JSONL 형식:

json
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}

변환 스크립트

python
import json
 
def convert_label_studio_to_potato(ls_file, potato_file):
    """Convert Label Studio JSON to Potato JSONL"""
 
    with open(ls_file, 'r') as f:
        ls_data = json.load(f)
 
    with open(potato_file, 'w') as f:
        for item in ls_data:
            potato_item = {
                "id": str(item["id"]),
                "text": item["data"].get("text", ""),
            }
 
            # Convert nested data fields
            if "data" in item:
                for key, value in item["data"].items():
                    if key != "text":
                        if "metadata" not in potato_item:
                            potato_item["metadata"] = {}
                        potato_item["metadata"][key] = value
 
            # Handle image URLs
            if "image" in item.get("data", {}):
                potato_item["image_url"] = item["data"]["image"]
 
            f.write(json.dumps(potato_item) + "\n")
 
    print(f"Converted {len(ls_data)} items")
 
# Usage
convert_label_studio_to_potato("label_studio_export.json", "data/items.jsonl")

주석 마이그레이션

기존 주석 변환하기

python
def convert_annotations(ls_export, potato_output):
    """Convert Label Studio annotations to Potato format"""
 
    with open(ls_export, 'r') as f:
        ls_data = json.load(f)
 
    with open(potato_output, 'w') as f:
        for item in ls_data:
            if "annotations" not in item or not item["annotations"]:
                continue
 
            for annotation in item["annotations"]:
                potato_ann = {
                    "id": str(item["id"]),
                    "text": item["data"].get("text", ""),
                    "annotations": {},
                    "annotator": annotation.get("completed_by", {}).get("email", "unknown"),
                    "timestamp": annotation.get("created_at", "")
                }
 
                # Convert results
                for result in annotation.get("result", []):
                    scheme_name = result.get("from_name", "unknown")
 
                    if result["type"] == "choices":
                        # Classification
                        potato_ann["annotations"][scheme_name] = result["value"]["choices"][0]
 
                    elif result["type"] == "labels":
                        # NER spans
                        if scheme_name not in potato_ann["annotations"]:
                            potato_ann["annotations"][scheme_name] = []
 
                        potato_ann["annotations"][scheme_name].append({
                            "start": result["value"]["start"],
                            "end": result["value"]["end"],
                            "label": result["value"]["labels"][0],
                            "text": result["value"]["text"]
                        })
 
                    elif result["type"] == "rating":
                        potato_ann["annotations"][scheme_name] = result["value"]["rating"]
 
                f.write(json.dumps(potato_ann) + "\n")
 
# Usage
convert_annotations("ls_annotated_export.json", "annotations/migrated.jsonl")

스팬 주석 변환

Label Studio는 문자 오프셋을 사용하고 Potato도 문자 오프셋을 사용하므로 변환이 간단합니다:

python
def convert_spans(ls_spans):
    """Convert Label Studio span format to Potato format"""
    potato_spans = []
 
    for span in ls_spans:
        potato_spans.append({
            "start": span["value"]["start"],
            "end": span["value"]["end"],
            "label": span["value"]["labels"][0],
            "text": span["value"]["text"]
        })
 
    return potato_spans

기능 매핑

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsbounding_box
PolygonLabelspolygon
Taxonomy(use nested multiselect)
Pairwisecomparison

품질 관리 고려 사항

품질 관리는 직접 손으로 작업해야 할 부분이 생기는 영역입니다. Potato에는 기본적인 QC가 있지만 하나로 통합된 포괄적인 시스템은 없으므로, 빈틈은 직접 메울 계획을 세우십시오.

품질 관리 접근 방식

주의 점검: 데이터 파일에 주의 점검 항목을 직접 추가할 수 있습니다. 이는 정답이 알려진 일반 항목으로, 안노테이터가 집중하고 있는지 확인하기 위해 포함합니다:

python
# Add attention check items to your data
attention_items = [
    {"id": "attn_1", "text": "ATTENTION CHECK: Please select 'Positive'", "is_attention": True},
    {"id": "attn_2", "text": "ATTENTION CHECK: Please select 'Negative'", "is_attention": True},
]
 
# Intersperse with regular items
import random
all_items = regular_items + attention_items
random.shuffle(all_items)

일치도 계산: 수집한 주석을 사용하여 안노테이터 간 일치도를 오프라인으로 계산합니다:

python
from sklearn.metrics import cohen_kappa_score
import numpy as np
 
def compute_agreement(annotations_file):
    """Compute agreement from collected annotations"""
    # Load annotations and compute metrics externally
    # Potato does not have built-in agreement calculation
    pass

중복 주석: 데이터 관리 과정에서 항목을 여러 사용자에게 배정하여 항목당 여러 안노테이터를 구성하십시오.

사용자 마이그레이션

Label Studio에서 사용자 내보내기

python
# Label Studio API call to get users
import requests
 
def export_ls_users(ls_url, api_key):
    response = requests.get(
        f"{ls_url}/api/users",
        headers={"Authorization": f"Token {api_key}"}
    )
    return response.json()

Potato 사용자 설정 만들기

yaml
user_config:
  # Simple auth for migrated users
  auth_type: password
 
  user_config:
    - username: user1@example.com
      password_hash: "..."  # Generate new passwords
 
    - username: user2@example.com
      password_hash: "..."

마이그레이션 테스트

검증 스크립트

python
def validate_migration(original_ls, converted_potato):
    """Validate converted data matches original"""
 
    with open(original_ls) as f:
        ls_data = json.load(f)
 
    with open(converted_potato) as f:
        potato_data = [json.loads(line) for line in f]
 
    # Check item count
    assert len(ls_data) == len(potato_data), "Item count mismatch"
 
    # Check IDs preserved
    ls_ids = {str(item["id"]) for item in ls_data}
    potato_ids = {item["id"] for item in potato_data}
    assert ls_ids == potato_ids, "ID mismatch"
 
    # Check text content
    for ls_item, potato_item in zip(
        sorted(ls_data, key=lambda x: x["id"]),
        sorted(potato_data, key=lambda x: x["id"])
    ):
        assert ls_item["data"]["text"] == potato_item["text"], \
            f"Text mismatch for item {ls_item['id']}"
 
    print("Validation passed!")
 
validate_migration("label_studio_export.json", "data/items.jsonl")

마이그레이션 체크리스트

  • Label Studio에서 데이터 내보내기(JSON 형식)
  • 템플릿 XML을 Potato YAML로 직접 변환
  • 데이터 형식을 변환하는 파이썬 스크립트 작성 및 실행(JSON에서 JSONL로)
  • 기존 주석을 변환하는 스크립트 작성 및 실행(있는 경우)
  • Potato 프로젝트 구조 설정
  • 샘플 데이터로 테스트
  • 변환된 데이터가 원본과 일치하는지 검증
  • 새 인터페이스에 대해 안노테이터 교육
  • 파일럿 주석 배치 실행

흔히 발생하는 문제

몇 가지가 사람들을 자주 걸려 넘어지게 합니다. 두 도구 모두 UTF-8을 사용하지만, 그래도 데이터에 인코딩 오류가 없는지 확인할 가치가 있습니다. 로컬 이미지 경로는 보통 URL로 바꾸거나 적어도 Potato가 기대하는 형식에 맞춰야 합니다. Label Studio에서 만든 커스텀 컴포넌트는 Potato 커스텀 템플릿으로 다시 만들어야 합니다. 그리고 Label Studio API를 대상으로 스크립트를 작성했다면, 그 스크립트는 대신 Potato의 API를 가리키도록 바꿔야 합니다.


마이그레이션에 도움이 필요하신가요? 전체 문서를 확인하거나 GitHub에서 문의하십시오.