본문으로 건너뛰기

크라우드 투표, 점, 상자의 합의 집계

Zooniverse 리듀서처럼 항목마다 여러 주석자의 답을 득표 비율, DBSCAN 점 군집, 중앙값 상자로 하나의 합의로 만듭니다.

v2.13.0에서 추가

consensus 내보내기는 Zooniverse 같은 시민 과학 프로젝트가 자원봉사자의 분류를 합치는 방식으로, 항목마다 여러 주석자의 답을 항목당 하나의 합의 답으로 합칩니다. 자원봉사 프로젝트의 각 대상은 여러 자원봉사자에게 가며, 예를 들어 군집 사진에서 펭귄 수를 세거나 카메라 트랩 프레임에서 동물을 표시합니다. 선택형 문항에서 내보내기는 각 답을 준 주석자 수를 보고하고 각 항목을 retired 또는 needs_review로 표시합니다. 점 표시에서는 주석자들의 점을 객체당 하나의 점으로 군집화하고, 경계 상자에서는 객체당 중앙값 상자 하나를 보고합니다. 시민 과학 밖의 크라우드소싱 팀도 항목당 여러 레이블을 항목당 하나의 레이블로 바꾸는 데 같은 내보내기를 사용합니다. Zooniverse의 용어를 따라 Potato는 한 항목의 문항에 답한 주석자를 그 항목의 분류자(classifier)라고 부릅니다.

  • 선택형 문항: radio, select, likert, multiselect 스킴은 모든 답에 대한 개수와 비율, 최다 답, 동률 여부, 상태를 얻습니다.
  • 숫자형 문항: slider와 number 스킴은 개수, 평균, 중앙값, 표준편차, 최솟값, 최댓값을 얻습니다.
  • 점: image_annotation 스킴의 landmark 표시는 DBSCAN으로 군집화하며, 반경은 픽셀 또는 이미지에 대한 비율로 정합니다.
  • 상자: image_annotation 스킴의 bbox 표시는 겹침으로 묶이고, 각 묶음은 중앙값 상자 하나를 얻습니다.
  • 설치: 내보내기에는 Potato 외에 필요한 것이 없습니다. 군집화는 numpy를 사용하며 Potato는 numpy를 핵심 의존성으로 설치합니다.

합의와 일치도

Potato는 주석자들이 얼마나 잘 일치하는지를 측정하고, 합의 내보내기는 항목당 답 하나를 만듭니다. reliability 내보내기와 /admin/iaa 페이지는 연구 전체에 걸친 문항별 Krippendorff의 알파를 보고하며, 알파가 낮은 팀은 먼저 지침이나 주석자 훈련을 살펴봅니다. 합의 내보내기는 항목별로 작동합니다. 각 항목은 그 항목의 주석자들이 답을 유지할 만큼 충분히 일치했는지 기록하는 상태를 얻습니다. 합의 내보내기는 주석자의 정확도와 관계없이 모든 주석자에게 한 표씩 주므로, 합의에 의존하기 전에 일치도를 확인하세요. 공간 레이블에 대한 우연 보정 일치도는 바운딩 박스의 주석자 간 일치도를 측정하는 방법을 참고하세요.

MACE는 모든 표를 똑같이 세는 대신 각 주석자의 표에 가중치를 둡니다. MACE는 radio, likert, select, multiselect 스킴에서 각 주석자의 역량 점수를 추정하고 가중된 표에서 각 항목의 레이블을 추론합니다. 크라우드 라벨 집계는 역량 모델이 가중치 없는 투표보다 나을 때와 답의 분포를 유지해야 할 때를 다룹니다. MACE는 점이나 상자 표시를 읽지 않습니다. 이미지의 표시에는 합의 내보내기를 사용하세요.

설정

아래 설정은 세 갈래 문항, 다중 선택 문항, 그리고 주석자가 성체 펭귄마다 점을 찍고 둥지마다 상자를 그리는 이미지 스킴이 있는 펭귄 개수 세기 워크플로를 구성합니다.

yaml
annotation_task_name: "Penguin colony counts"
task_dir: .
data_files: [data/colonies.jsonl]
item_properties: {id_key: id, text_key: image}
output_annotation_dir: annotation_output
num_annotators_per_item: 5
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: radio
    name: T0
    description: "Are there penguins in the image?"
    labels: ["Yes", "No", "Unsure"]
  - annotation_type: multiselect
    name: T1
    description: "What else is in the image?"
    labels: [Ice, Water, Rock]
  - annotation_type: image_annotation
    name: T2
    description: "Mark each adult penguin and draw a box round each nest"
    source_field: image
    tools: [landmark, bbox]
    labels:
      - {name: Adult, color: "#00ff00"}
      - {name: Nest, color: "#ff0000"}
 
consensus_aggregation:
  agreement_threshold: 0.8
  min_classifiers: 2
  points:
    eps: 10
    eps_unit: pixels
    min_samples: 2
  boxes:
    iou_threshold: 0.5

스킴 이름 T0, T1, T2는 Zooniverse의 과제 ID입니다. potato import는 프로젝트가 Zooniverse에서 올 때 과제 ID를 스킴 이름으로 유지하며, Potato에서 만든 연구는 아무 이름이나 쓸 수 있습니다. Potato는 각 이미지를 주석자 다섯 명에게 보냅니다(num_annotators_per_item: 5). agreement_threshold: 0.8이면 항목이 retired가 되려면 다섯 명 중 네 명이 같은 답을 주어야 합니다. min_classifiers: 2이면 주석자 한 명의 답은 합의로 인정되지 않습니다. 서로 10픽셀 이내에 있는 두 점은 군집을 시작할 수 있으며, 두 상자는 교집합 대 합집합 비율(IoU)이 0.5 이상으로 겹칠 때에만 한 둥지에 속합니다.

픽셀 반경으로 군집화하려면 내보내기에 각 이미지의 크기가 필요합니다. 각 항목은 이미지와 그 너비 및 높이(픽셀)를 지정합니다.

json
{"id": "9001", "image": "/media/colony_9001.png", "image_width": 1000, "image_height": 500}

블록의 모든 키는 선택 사항입니다.

키기본값--option 이름의미
agreement_threshold0.8agreement_threshold 또는 threshold항목이 retired가 되기 위해 최다 답이 필요로 하는 분류자의 비율. 0보다 크고 1 이하
min_classifiers2min_classifiersneeds_review 이외의 상태를 받기 위한 최소 분류자 수. 1 이상의 정수
schemes지원하는 모든 스킴schemes(쉼표로 구분)집계할 스킴 이름. 각 이름은 설정이 선언한 스킴이어야 합니다
points.eps0.02eps군집 반경. 0보다 크며, eps_unit이 fraction이면 1 이하
points.eps_unitfractioneps_unitfraction은 x를 이미지 너비에 대한 비율로, y를 높이에 대한 비율로 측정하고, pixels는 이미지 픽셀로 측정합니다
points.min_samples2min_samples한 점이 핵심점이 되기 위해 그 점의 eps 이내에 있어야 하는 점의 최소 수. 그 점 자신도 센다. 1 이상의 정수
boxes.iou_threshold0.5iou_threshold한 묶음 안의 임의의 두 상자 사이의 최소 IoU. 0보다 크고 1 이하

값이 범위를 벗어나거나, 키가 블록의 목록에 없거나, schemes 항목이 설정에 선언되지 않은 스킴을 지정하면 서버는 시작할 때 중지하고 그 키를 밝히는 메시지를 출력합니다. python -m potato.validate_cli config.yaml은 연구를 시작하기 전에 같은 오류를 보고합니다.

내보내기 명령

내보내기는 설정과 output_annotation_dir 아래에 저장된 주석을 읽습니다.

bash
python -m potato.export -c config.yaml -f consensus -o consensus_out

이 명령은 consensus_out/에 consensus.json과 설정의 스킴 유형별 CSV 파일을 씁니다. 설정을 바꾸지 않고 한 번의 실행에서 블록의 한 설정을 재정의하려면 --option을 전달합니다.

bash
python -m potato.export -c config.yaml -f consensus -o consensus_out \
    --option agreement_threshold=0.9 --option eps=8 --option eps_unit=pixels

내보내기는 알 수 없는 옵션 이름에서 중지하고 알려진 이름의 목록을 출력합니다. 설정에 지원하는 유형의 스킴이 없거나 저장된 주석이 없을 때도 내보내기는 중지하고 이유를 출력합니다. /admin/api/export의 관리자 내보내기 API는 서버에 셸 접근 권한이 없는 팀을 위해 같은 내보내기를 형식 이름 consensus로 제공합니다.

선택형 문항

항목과 문항마다 내보내기는 분류자와 각 답을 준 주석자를 셉니다. 각 답의 비율은 그 답의 개수를 분류자 수로 나눈 값입니다. 단일 답 문항에서는 비율의 합이 1입니다. multiselect에서는 각 비율이 그 선택지에 체크한 분류자의 비율이며 비율의 합이 1을 넘을 수 있습니다. consensus_questions.csv는 항목과 문항마다 한 행을 가집니다. 아래 행들은 이 페이지 끝에서 설명하는 예제 프로젝트에서 나온 것입니다.

csv
instance_id,schema,type,n_classifiers,top_answer,top_count,top_fraction,tied,status,reason
9001,T0,radio,5,Yes,4,0.8,,retired,
9001,T1,multiselect,5,Ice,4,0.8,,needs_review,no agreement on Water (ticked by more than 20% and fewer than 80% of classifiers)
9002,T0,radio,4,Yes,2,0.5,Yes;No,needs_review,"tie between Yes, No"
9002,T1,multiselect,4,Rock,4,1.0,,retired,
9003,T0,radio,1,Yes,1,1.0,,needs_review,"1 classifier, fewer than min_classifiers (2)"
9003,T1,multiselect,1,Ice,1,1.0,,needs_review,"1 classifier, fewer than min_classifiers (2)"

상태는 세 가지 규칙을 따릅니다.

  • 분류자가 너무 적음: 분류자가 min_classifiers보다 적은 항목은 needs_review입니다.
  • 단일 답 문항: radio, select, likert 항목은 최다 답의 비율이 agreement_threshold 이상이고 다른 답이 최다 답과 동률이 아닐 때 retired입니다.
  • 다중 선택 문항: 모든 선택지가 결정되면 항목은 retired입니다. 분류자의 agreement_threshold 이상이 선택지에 체크했거나, 그 비율 이상이 체크하지 않았으면 선택지가 결정된 것입니다.

이미지 9001에서는 주석자 다섯 명 중 두 명이 Water에 체크했습니다. 따라서 Water 선택지는 결정되지 않은 채 남는데, Water에 체크한 40%도, 체크하지 않은 60%도 0.8 임계값에 이르지 못하기 때문입니다. 답이 동률이면 top_answer는 스킴이 레이블을 선언한 순서에서 처음 동률인 답을 가리키고, tied는 동률인 모든 답을 세미콜론으로 이어 나열합니다. reason 열은 항목에 검토가 필요한 이유를 알려 줍니다.

합의 상태는 보고서이며 Potato는 그 상태에 따라 아무 조치도 하지 않습니다. Zooniverse는 정해진 수의 분류 뒤에 대상을 퇴역시키지만, Potato는 합의 상태와 관계없이 연구의 배정 규칙에 따라 계속 항목을 배정합니다. 더 많은 주석자나 판정으로 보낼 항목을 고르는 데 이 상태를 사용하세요.

consensus_votes.csv는 항목, 문항, 답마다 한 행을 가지며 열은 instance_id, schema, answer, count, fraction, n_classifiers입니다. 스킴이 선언한 모든 선택지가 행을 얻으며, 주석자가 그 선택지를 고르지 않았으면 개수는 0입니다.

csv
instance_id,schema,answer,count,fraction,n_classifiers
9001,T0,Yes,4,0.8,5
9001,T0,No,1,0.2,5
9001,T0,Unsure,0,0.0,5
9001,T1,Ice,4,0.8,5
9001,T1,Water,2,0.4,5
9001,T1,Rock,1,0.2,5

선택지는 나열되어 있지만 체크된 것이 없는 저장된 다중 선택 레코드는 표가 없는 분류자로 셉니다. 이런 레코드는 n_classifiers에 1을 더하고 어떤 답의 개수에도 더하지 않습니다. 문항에 대한 항목이 없는 레코드는 분류자로 세지 않습니다.

슬라이더와 숫자 문항

consensus_numeric.csv는 항목과 문항마다 한 행을 가지며 열은 instance_id, schema, type, n, mean, median, sd, min, max입니다. n은 숫자 답을 준 주석자의 수입니다. sd는 n − 1을 분모로 하는 표본 표준편차입니다. 주석자 한 명만 답했으면 sd는 비어 있습니다. 숫자형 문항은 상태를 얻지 않습니다.

점 군집

DBSCAN(density-based spatial clustering of applications with noise)은 서로 가까이 있는 점을 묶고 고립된 점은 잡음으로 제외합니다. 내보내기는 한 항목의 한 레이블에 대한 모든 주석자의 점을 모으고 모은 점을 DBSCAN으로 군집화합니다.

  • 어떤 점은 그 점과 그 점의 eps 이내에 있는 다른 점의 수가 min_samples 이상이면 핵심점입니다.
  • 군집은 연결된 핵심점들의 묶음과, 핵심점 하나의 eps 이내에 있는 다른 점들입니다. 두 군집 옆에 있는 점은 주석이 불러와진 순서대로 먼저 발견된 군집에 합류합니다.
  • 남은 모든 점은 잡음입니다.

이 규칙은 scikit-learn의 DBSCAN이 구현하는 것이며 panoptes_aggregation의 리듀서 point_reducer_dbscan이 호출하는 것입니다. eps_unit: pixels와 같은 eps 및 min_samples를 쓰면 내보내기는 point_reducer_dbscan과 같은 군집과 중심을 줍니다. panoptes_aggregation의 기본값은 eps 5픽셀과 min_samples 3이지만, Potato의 기본값은 항목당 소수의 주석자에 맞춰져 있습니다. 이미지당 자원봉사자가 많으면 min_samples를 높이세요.

consensus_points.csv는 항목, 스킴, 레이블, 군집마다 한 행을 가집니다.

csv
instance_id,schema,label,cluster,x,y,x_px,y_px,size,n_annotators,spread,spread_px,var_x,var_y,cov_xy,annotators,label_points,label_annotators,label_noise,n_classifiers,note
9001,T2,Adult,0,0.10025,0.201,100.25,100.5,4,4,0.003897,2.331845,2.916667,4.333333,-1.166667,vol1;vol2;vol3;vol4,8,4,1,4,
9001,T2,Adult,1,0.800667,0.8,800.666667,400.0,3,3,0.004643,3.681787,16.333333,4.0,8.0,vol1;vol2;vol4,8,4,1,4,
9002,T2,Adult,,,,,,,,,,,,,,1,1,1,1,no cluster (every point is noise)

이미지 9001에서 주석자 네 명이 Adult 점 여덟 개를 찍었습니다. 그중 일곱 개는 군집 두 개를 이루며 각각 성체 한 마리이고, 여덟 번째 점은 잡음입니다. 열은 다음과 같습니다.

열의미
cluster항목의 레이블 안에서 0부터 시작하는 군집 번호
x, y구성 점의 평균인 군집 중심이며 이미지 너비와 높이에 대한 비율
x_px, y_px픽셀로 나타낸 같은 중심이며 이미지 크기를 모르면 비어 있음
size군집의 점 수
n_annotators, annotators군집에 점을 가진 서로 다른 주석자의 수와 그 주석자의 이름
spread, spread_px구성 점이 중심에서 떨어진 거리의 제곱평균제곱근이며 비율과 픽셀로 표시
var_x, var_y, cov_xyx와 y의 표본 분산 및 x와 y의 공분산이며 이미지 크기를 알면 픽셀 단위. 값은 Zooniverse 리듀서의 clusters_var_x, clusters_var_y, clusters_var_x_y 열과 일치합니다
label_points, label_annotators, label_noise항목의 레이블 전체에 대한 점, 주석자, 잡음점의 수이며 레이블의 각 행에 반복됩니다
n_classifiers항목에서 이미지 스킴을 저장한 주석자 수
note레이블에 군집이 없는 이유

점이 모두 잡음인 레이블도 이미지 9002처럼 cluster가 비어 있고 note가 있는 행 하나를 얻습니다. DBSCAN은 한 주석자의 점 두 개가 한 군집에 들어가는 것을 막지 않습니다. 그런 군집을 찾으려면 size를 n_annotators와 비교하세요.

이미지 크기와 Zooniverse 가져오기

Potato는 모든 표시를 이미지 너비와 높이에 대한 비율로 저장합니다. 따라서 eps_unit: fraction이면 내보내기는 이미지 크기 없이도 모든 항목의 점을 군집화합니다. 1000 × 500 픽셀 이미지에서 eps 0.02는 가로 20픽셀, 세로 10픽셀에 해당합니다.

픽셀 출력과 eps_unit: pixels에는 내보내기가 이미지 크기를 필요로 합니다. 내보내기는 항목의 image_width와 image_height 필드(또는 width와 height)에서 크기를 가져옵니다. 그 필드가 없으면 이미지 파일이 작업의 미디어 디렉터리 아래에 있을 때 파일 자체에서 크기를 읽습니다. 원격 이미지는 내려받지 않습니다. eps_unit: pixels에서 크기를 모르는 항목의 레이블은 "image size unknown; pixel eps cannot be applied"라는 메모가 있는 행을 얻고, 내보내기는 그런 항목을 최대 다섯 개까지 밝히는 경고를 출력합니다.

Zooniverse 대상은 Zooniverse 서버에 호스팅된 이미지이며 내보내기는 그 서버의 이미지를 열 수 없습니다. Potato 2.13.0부터 potato import는 그리기 과제가 있는 프로젝트의 각 항목에 image_width와 image_height를 씁니다. 크기는 Zooniverse가 분류와 함께 기록한 이미지 치수 또는 대상 메타데이터의 너비와 높이에서 옵니다. 따라서 가져온 그림은 팀이 panoptes_aggregation에서 사용한 것과 같은 eps로 픽셀 단위로 군집화됩니다. --keep-annotators로 가져오세요. 이 플래그가 있으면 가져오기 도구가 각 자원봉사자의 분류를 그 자원봉사자가 저장한 작업으로 쓰며, 그러면 합의 내보내기는 각 자원봉사자를 한 번씩 셉니다.

bash
potato import -i zooniverse-exports/ -o my-project --keep-annotators
python -m potato.export -c my-project/config.yaml -f consensus -o consensus_out

로그인하지 않은 자원봉사자는 anon_<id>로 나타나며, 이 경우 한 사람이 여러 분류자로 셀 수 있습니다. 기록된 크기가 없는 대상의 표시는 가져올 때 제외되며, 가져오기는 제외한 표시의 수를 세고 경고를 출력합니다. Zooniverse 가져오기 절이 가져오기 도구가 읽는 과제 유형을 나열합니다.

합의 상자

교집합 대 합집합(IoU)은 두 상자가 공유하는 면적을 두 상자가 함께 덮는 면적으로 나눈 값입니다. 내보내기는 한 항목의 한 레이블에 대한 상자를 묶음으로 나눕니다. 한 묶음은 주석자당 최대 상자 하나를 담으며, 묶음 안의 모든 상자 쌍은 최소 iou_threshold만큼 겹칩니다. 내보내기는 세 단계로 묶음을 만듭니다.

  1. 남은 각 상자가 후보 묶음을 시작하며, 다른 주석자의 남은 상자를 첫 상자와의 IoU가 높은 순서대로 시도합니다. 상자는 묶음에 이미 있는 모든 상자와의 IoU가 임계값 이상이면 묶음에 합류합니다.
  2. 가장 큰 후보 묶음을 유지합니다. 동률이면 첫 상자와의 IoU 합이 큰 묶음이, 그다음에는 앞선 상자의 묶음이 선택됩니다.
  3. 유지한 묶음의 상자는 풀에서 빠지고, 남은 상자에 대해 단계를 반복합니다. 다른 어떤 상자와도 겹치지 않는 상자는 하나짜리 묶음이 됩니다.

각각 세 번째 상자와 겹치는 두 상자가 서로 겹치지 않으면 서로 다른 묶음에 들어갑니다. 합의 상자는 구성 상자의 왼쪽, 위쪽, 오른쪽, 아래쪽 가장자리의 중앙값입니다. IoU는 이미지를 늘려도 바뀌지 않습니다. 따라서 묶는 데는 이미지 크기가 필요 없고, 크기를 알면 픽셀 열이 채워집니다.

consensus_boxes.csv는 항목, 스킴, 레이블, 묶음마다 한 행을 가집니다.

csv
instance_id,schema,label,group,x,y,width,height,x_px,y_px,width_px,height_px,support,n_classifiers,support_fraction,mean_iou,min_iou,annotators
9001,T2,Nest,0,0.2,0.2,0.1,0.11,200.0,100.0,100.0,55.0,3,4,0.75,0.85617,0.826087,vol1;vol2;vol3
9001,T2,Nest,1,0.6,0.6,0.05,0.1,600.0,300.0,50.0,50.0,1,4,0.25,1.0,1.0,vol4
9003,T2,Nest,0,0.01,0.04,0.03,0.08,10.0,20.0,30.0,40.0,1,1,1.0,1.0,1.0,vol1

x, y, width, height는 이미지에 대한 비율로 나타낸 합의 상자이고, _px 열은 같은 상자를 픽셀로 나타냅니다. support는 묶음의 주석자 수이고, n_classifiers는 항목에서 이미지 스킴을 저장한 주석자 수이며, support_fraction은 support를 n_classifiers로 나눈 값입니다. mean_iou와 min_iou는 각 구성 상자를 합의 상자와 비교합니다. 이미지 9001에서 주석자 네 명 중 세 명이 첫 번째 둥지를 중앙값 상자에 대한 평균 IoU 0.86으로 그렸고, 한 주석자의 상자는 임계값 이상으로 겹치는 다른 상자가 없어 하나짜리 묶음을 이룹니다.

이 묶는 방식은 Potato 고유의 것이며 Zooniverse 상자 리듀서의 포팅이 아닙니다. panoptes_aggregation의 rectangle_reducer는 픽셀 단위의 각 상자의 x, y, 너비, 높이에 DBSCAN을 실행합니다. metric_type: IoU인 shape_reducer_dbscan은 1 − IoU를 거리로 하는 DBSCAN을 실행하며, 겹치는 상자의 연쇄는 서로 겹치지 않는 두 상자를 합칠 수 있습니다. 따라서 같은 분류에 대해서도 Potato와 Zooniverse의 상자 합의는 다를 수 있습니다.

텍스트와 그 밖의 답 유형

합의 내보내기는 일곱 가지 스킴 유형을 읽습니다. radio, select, likert, multiselect, slider, number, image_annotation입니다. 자유 텍스트, 스팬, 그 밖의 모든 스킴 유형은 경고 없이 합의에서 제외됩니다. 목록을 더 좁히려면 schemes를 설정하세요. 텍스트 답에는 셀 표가 없으며, 표 형식 내보내기는 나란히 읽거나 판정에 쓸 수 있도록 모든 주석자의 텍스트를 유지합니다.

image_annotation 스킴 안에서 내보내기는 점과 상자만 집계합니다. 다각형, 폴리라인, 타원, 자유형 도형, 마스크, 키포인트 집합은 유형별로 세며, 내보내기는 그 개수를 경고에 밝힙니다. consensus.json은 같은 개수를 not_aggregated 아래에 기록합니다.

출력 파일

파일한 행의 단위작성되는 경우
consensus_questions.csv항목과 문항설정에 radio, select, likert, multiselect 스킴이 있을 때
consensus_votes.csv항목, 문항, 답위와 같음
consensus_numeric.csv항목과 문항설정에 slider 또는 number 스킴이 있을 때
consensus_points.csv항목, 스킴, 레이블, 점 군집설정에 image_annotation 스킴이 있을 때
consensus_boxes.csv항목, 스킴, 레이블, 상자 묶음위와 같음
consensus.json위의 결과를 중첩하여, 사용한 설정, not_aggregated 개수, 경고와 함께항상

CSV 파일의 값은 소수점 아래 여섯 자리로 반올림됩니다. annotators와 tied 같은 목록은 세미콜론으로 이어집니다. consensus.json은 전체 값과 문항별 분류자 목록을 유지합니다.

예제

bash
python potato/flask_server.py start examples/image/zooniverse-aggregation/config.yaml -p 8000

이 예제는 Zooniverse 내보내기에서 가져온 가상의 펭귄 개수 세기 워크플로로, 군집 이미지 네 장에 대한 자원봉사자 다섯 명의 저장된 분류가 있습니다. 이 페이지의 CSV 행은 예제의 합의 내보내기에서 나온 것이며, Potato 체크아웃에서 python -m potato.export -c examples/image/zooniverse-aggregation/config.yaml -f consensus -o consensus_out으로 실행한 결과입니다.

더 읽을거리

구현 세부 사항은 소스 문서를 참고하세요.