Label Studio에서 Potato로 마이그레이션하기
이 단계별 가이드로 Label Studio에서 Potato로 마이그레이션하면서 프로젝트 설정, 주석 스키마, 내보낸 데이터를 변환하고 일반적인 주석 유형을 모두 다룹니다.
이 가이드는 기존 Label Studio 프로젝트를 Potato로 옮기는 과정을 차근차근 설명합니다. 먼저 일러둘 것이 있습니다. 공식 마이그레이션 도구는 없습니다. 설정은 직접 손으로 변환하고 데이터를 재구성하려면 약간의 파이썬 코드를 작성해야 하므로, 두 플랫폼 모두에 익숙해야 합니다.
기능을 나란히 비교한 내용과 Potato 자체 마이그레이션 도구는 원본 문서와 마이그레이션 CLI 문서를 참고하십시오.
왜 마이그레이션해야 할까요?
어떤 프로젝트에는 Potato가 더 잘 맞습니다. Potato는 학술 주석 연구를 위해 만들어졌고, Prolific 및 MTurk 연동을 기본으로 제공하며, 띄울 데이터베이스 없이 YAML로 설정합니다. 파이썬으로 확장하기 쉽고, 저장소가 그냥 파일이기 때문에 배포하기도 쉽습니다.
마이그레이션 개요
이 과정은 수동으로 진행되며 대략 다음과 같이 흘러갑니다.
- Label Studio XML 템플릿을 Potato YAML 설정으로 직접 변환합니다
- 데이터 형식을 변환하는 파이썬 스크립트를 작성합니다(JSON에서 JSONL로)
- 기존 주석을 마이그레이션하는 스크립트를 작성합니다(있는 경우)
- 충분히 테스트하고 변환된 데이터를 검증합니다
템플릿 변환
텍스트 분류
Label Studio 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:
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:
<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:
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:
<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:
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:
<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:
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:
<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:
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:
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>Potato 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 형식:
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]Potato JSONL 형식:
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}변환 스크립트
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")주석 마이그레이션
기존 주석 변환하기
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도 문자 오프셋을 사용하므로 변환이 간단합니다:
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 Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (use nested multiselect) |
| Pairwise | comparison |
품질 관리 고려 사항
품질 관리는 직접 손으로 작업해야 할 부분이 생기는 영역입니다. Potato에는 기본적인 QC가 있지만 하나로 통합된 포괄적인 시스템은 없으므로, 빈틈은 직접 메울 계획을 세우십시오.
품질 관리 접근 방식
주의 점검: 데이터 파일에 주의 점검 항목을 직접 추가할 수 있습니다. 이는 정답이 알려진 일반 항목으로, 안노테이터가 집중하고 있는지 확인하기 위해 포함합니다:
# 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)일치도 계산: 수집한 주석을 사용하여 안노테이터 간 일치도를 오프라인으로 계산합니다:
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에서 사용자 내보내기
# 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 사용자 설정 만들기
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: "..."마이그레이션 테스트
검증 스크립트
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를 가리키도록 바꿔야 합니다.