리얼월드
경력근거: 로보틱스 파운데이션 모델(Robotics Foundation Model)을 개발하고 있는
리얼월드의 Forward Deployed ML Engineer 는 현장 데이터로 파운데이션 모델을 미세조정하고 도메인 적응을 수행하면서, 고객 환경에 맞는 데이터 수집 전략을 세우고 정제한 학습용 데이터셋을 내부 ML팀에 넘기는 자리입니다. 이 자리의 첫 병목은 모델이 아니라 현장에서 올라온 수집물의 품질이고, 고자유도 로봇 손의 조작 시연을 공장에서 모으는 맥락이 있어야 카메라 세 대 중 손목 카메라만 프레임이 빠지거나 같은 작업이 회차마다 다른 이름으로 적히는 결함이 실제로 등장합니다. 고객과 SI 파트너를 직접 상대하는 파견 자리에서는 처음부터 여러 사람이 보는 화면을 만들기보다 담당자 한 명이 수집 직후 터미널에서 돌려 보는 CLI로 기능을 검증하는 편이 자연스러워, 이 포트폴리오도 그 형태로 끝까지 갑니다. 이 포트폴리오가 보여주는 것은 결함 판정 규칙 설계와 근거 인용 검증과 문서 자동 생성 기술이며, 로보틱스 파운데이션 모델 학습이나 모방학습 데이터 설계 같은 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
현장에서 모은 데이터를 정제해 학습용 데이터셋으로 만들어 내부 ML팀에 넘기는 것이 주요 업무라서, 수집 에피소드를 학습에 반입하기 전에 규칙으로 걸러 내는 검사를 제품의 골조로 둡니다.
고객 환경을 기준으로 데이터 수집 전략을 세우고 실행하며 고객과 SI 파트너를 상대하는 자리라서, 입력을 고객사 수집 리그가 내려 준 목록 파일로 두고 산출물을 현장 담당자에게 보낼 확인 질문 문서로 둡니다.
불확실한 상황을 구조화해 실행 가능한 방향으로 바꾸는 태도를 요구하는 자리라서, 애매한 수집 결과를 그대로 두지 않고 누구에게 무엇을 물어 어떤 조치를 고르게 할지까지 문서로 못박습니다.
제조 고객사 현장에서 텔레오퍼레이션으로 모은 로봇 시연 에피소드 목록 파일과 기록 메타 파일을 읽어, 학습 데이터셋으로 넘기기 전에 걸리는 결함을 규칙으로 판정하는 CLI입니다. 터미널에서 check 명령에 두 파일과 수집 회차를 주면, 작업 라벨이 비었거나 사전에 없는 표기로 적혔거나 카메라 한 대의 프레임이 빠졌거나 성공 표기와 중단 사유가 어긋난 에피소드가 규칙별로 묶이고, 현장 담당자와 SI 파트너가 그대로 답할 확인 질문과 조치 선택지가 절 단위로 갖춰진 재수집 요청 Markdown 파일이 생깁니다. 표준출력에는 규칙별 걸린 건수와 반입 가능 건수가 표로 찍히고, 질문마다 어느 필드의 어떤 값을 근거로 걸렸는지 함께 적어 현장에서 이유를 물으면 담당자가 바로 답하게 합니다.
제조 고객사 공장에 파견 나가 로봇 시연 데이터 수집을 맡은 엔지니어입니다. 수집이 끝난 밤에 고객사 수집 PC 에서 내려받은 에피소드 목록과 기록 메타를 엑셀로 열어 눈으로 훑다가, 카메라 한 대가 중간에 끊긴 회차를 학습 데이터셋으로 넘긴 뒤에야 ML팀 연락으로 알게 되던 사람이, 터미널에서 명령 한 번을 치고 무엇이 왜 걸렸는지와 내일 아침 현장 담당자에게 물을 질문이 정리된 요청서 파일을 받고 싶어 합니다.
수집 에피소드의 결함은 대부분 라벨 누락과 표기 흔들림, 프레임 수와 기록 시간의 산술 불일치, 성공 표기와 중단 사유의 모순처럼 파일 안에서 규칙으로 판정되는 모양이라서, 사람이 눈으로 훑는 대신 규칙이 걸러 내고 사람은 남은 항목을 현장에 물어보는 데만 시간을 쓰면 된다는 가정입니다.
고객사 수집 리그가 내려 준 형식의 에피소드 목록 더미와 기록 메타 더미를 놓고 터미널에서 check 명령에 수집 회차를 주면, 라벨 누락과 표기 미등록과 프레임 결손과 성공 표기 모순을 규칙으로 판정해 규칙별로 묶고, 현장 담당자와 SI 파트너가 답할 확인 질문과 조치 선택지가 절 단위로 갖춰진 재수집 요청 Markdown 파일을 쓰며 표준출력에 규칙별 건수 표를 찍는 CLI가 도는 완결품입니다. 만드는 덩어리는 결함 판정 규칙과 요청서 문서 생성 둘뿐이고 서버와 화면과 실시간 연동이 없어 평일 5일에 혼자 끝나는 범위이며, 데이터는 실제 고객사 수집물 대신 작성자가 만든 더미를 씁니다.
현장 담당자가 요청서에 답해 돌려준 회신 파일을 apply 하위 명령으로 물려 규칙을 다시 돌리고, 해소된 항목과 아직 남은 항목을 나눠 남은 것만 담은 2차 요청서를 내는 계층입니다. 담당자가 자유 문장으로 쓴 비고는 LLM 이 조치 분류 초안으로 옮기되 근거가 된 회신 문장을 글자 그대로 인용하게 하고, 그 인용이 원문에 실재하는지 대조해 실재하지 않으면 확인 필요 절로 돌립니다.
도구를 Docker 이미지로 묶어 고객사 수집 PC 의 리눅스 환경에서 수집 직후 같은 버전으로 돌게 하고, 통과 판정을 받은 에피소드 목록을 PyTorch Dataset 으로 여는 스모크 테스트를 붙여 학습 반입 준비 상태를 보이는 확장입니다. 회차별 판정 기록을 쌓아 사이트와 작업별 결함 비율을 계산하고, 반복되는 결함을 수집 절차 개선 논의의 입력으로 넘깁니다.
에피소드 12건과 기록 메타 12건을 놓고 터미널에서 check 명령에 수집 회차를 주면, 결함이 규칙별로 묶이고 확인 질문과 조치 선택지가 붙은 재수집 요청 Markdown 파일이 생기며 표준출력에 규칙별 건수와 반입 가능 건수가 표로 찍히는 흐름을 시연합니다.
라벨이 빈 회차와 사전에 없는 표기와 카메라 한 대만 프레임이 0인 회차와 기록 시간이 재생 길이와 어긋난 회차를 섞은 입력 30종을 넣어도 도구가 죽지 않고 확인 필요 절로 분리하는 것을 보입니다. 다만 데이터와 정답을 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
같은 입력에서 언제나 같은 요청서 파일이 나오는 결정론을 테스트로 보이고, 심각도가 blocker 인 항목이 하나라도 있으면 종료 코드로 알려 학습 반입 스크립트가 이어받지 않게 합니다.
2주차에는 담당자 회신 30건을 물려 재판정했을 때 해소로 바뀐 항목이 전건 규칙 재계산과 일치하는지 확인하고, 자유 문장 비고에서 옮긴 인용 가운데 원문에 실재하지 않는 건수 0건을 통과 기준으로 삼습니다. 이 기준 역시 회신을 작성자가 만든 합성 조건에서의 동작입니다.
3주차에는 회차별 판정 기록을 쌓아 사이트별 결함 비율을 계산하고, 통과분 목록이 PyTorch Dataset 으로 예외 없이 열리는지 확인합니다.
위 수치는 더미데이터 자가채점이며, 실제 결함 적중률은 리얼월드가 고객사 현장에서 모은 실제 수집물과 현장 담당자의 회신이 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모가 도는 상태와 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 라벨이 빈 회차와 한글과 영문이 섞인 표기 흔들림과 카메라 한 대만 죽은 회차와 기록 시간이 어긋난 회차와 성공 표기가 중단 사유와 모순인 회차를 더미에 일부러 섞어 두었습니다.
실제 현장의 결함은 파일 밖에도 있어, 조명이 바뀌었거나 치구 위치가 달라진 회차는 파일만 봐서 걸리지 않습니다. 파일 안에서 판정되는 결함만 다루는 것이 이 도구의 경계이고, 그 밖은 현장 확인 질문으로 넘깁니다. 표기 흔들림을 결함으로 잡는 과탐도 실제 사전이 붙어야 다듬을 수 있습니다.
이 포트폴리오가 보여주는 것은 결함 판정 규칙 설계와 근거 인용 검증과 문서 자동 생성 기술이며, 로보틱스 파운데이션 모델 학습이나 모방학습 데이터 설계나 6D 자세 추정 같은 도메인 깊이는 주장하지 않습니다. 모르는 부분은 면접에서 모른다고 말합니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
고객사 공장에서 텔레오퍼레이션으로 모은 로봇 시연 에피소드 목록 더미입니다. 현장 작업자와 SI 파트너가 손으로 채운 라벨과 성공 표기가 섞여 있어, CLI가 값 자체의 결함을 판정하는 입력 파일이 됩니다.
| episode_id | site | robot_id | task_label_raw | operator | recorded_at | duration_s | success_flag | abort_reason | calib_id | operator_note |
|---|---|---|---|---|---|---|---|---|---|---|
| EP-1001 | 화성 2공장 | RB-A1 | 커넥터 삽입 | 김수집 | 2026-07-27T09:12:04 | 18.4 | true | — | CAL-0727-A | — |
| EP-1002 | 화성 2공장 | RB-A1 | 커넥터삽입 | 김수집 | 2026-07-27T09:18:41 | 16.9 | true | — | CAL-0727-A | — |
| EP-1003 | 화성 2공장 | RB-A1 | connector insert | 이현장 | 2026-07-27T10:02:15 | 21.2 | false | grip_slip | CAL-0727-A | 세 번째 시도에서 미끄러졌습니다 |
| EP-1004 | 화성 2공장 | RB-A2 | — | 이현장 | 2026-07-27T10:31:50 | 12 | — | — | CAL-0727-A | 라벨은 나중에 채우겠습니다 |
| EP-1005 | 화성 2공장 | RB-A2 | 케이블 정렬 | 박파트너 | 2026-07-27T11:05:09 | 0.4 | false | estop | CAL-0727-A | — |
아래 JSON Schema에 맞는 수집 에피소드 행을 25건 더 만들어 주세요. {"type":"object","properties":{"episode_id":{"type":"string"},"site":{"type":"string"},"robot_id":{"type":"string"},"task_label_raw":{"type":["string","null"]},"operator":{"type":["string","null"]},"recorded_at":{"type":"string"},"duration_s":{"type":"number"},"success_flag":{"type":["boolean","null"]},"abort_reason":{"type":["string","null"]},"calib_id":{"type":["string","null"]},"operator_note":{"type":["string","null"]}},"required":["episode_id","site","robot_id","recorded_at","duration_s"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 task_label_raw 가 null 이라 작업이 무엇인지 알 수 없는 행, 둘째 같은 작업이 띄어쓰기와 한글 영문과 괄호 표기로 갈라진 행, 셋째 success_flag 가 true 인데 abort_reason 이 채워진 모순 행, 넷째 success_flag 가 null 인 채 operator_note 에 애매하다고 적힌 행, 다섯째 duration_s 가 1초 미만이거나 300초를 넘는 극단 행, 여섯째 calib_id 가 null 이거나 수집일보다 2주 이상 앞선 날짜로 적힌 행, 일곱째 operator 가 null 인 행. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"episode_id":{"type":"string"},"site":{"type":"string"},"robot_id":{"type":"string"},"task_label_raw":{"type":["string","null"]},"operator":{"type":["string","null"]},"recorded_at":{"type":"string"},"duration_s":{"type":"number"},"success_flag":{"type":["boolean","null"]},"abort_reason":{"type":["string","null"]},"calib_id":{"type":["string","null"]},"operator_note":{"type":["string","null"]}},"required":["episode_id","site","robot_id","recorded_at","duration_s"]}에피소드마다 수집 리그가 남긴 기록 요약 더미입니다. 카메라별 프레임 수와 관절 샘플 수와 기록 시각이 들어 있어, CLI가 재생 길이와 산술로 대조해 프레임 결손과 시각 불일치를 판정하는 입력 파일이 됩니다.
| episode_id | expected_fps | cam_top_frames | cam_wrist_frames | cam_side_frames | joint_samples | bag_start | bag_end | size_mb | checksum_ok |
|---|---|---|---|---|---|---|---|---|---|
| EP-1001 | 30 | 552 | 552 | 551 | 1,840 | 2026-07-27T09:12:04 | 2026-07-27T09:12:22 | 902.4 | true |
| EP-1002 | 30 | 507 | 507 | 507 | 1,690 | 2026-07-27T09:18:41 | 2026-07-27T09:18:58 | 831 | true |
| EP-1003 | 30 | 636 | 402 | 636 | 2,120 | 2026-07-27T10:02:15 | 2026-07-27T10:02:36 | 988.7 | true |
| EP-1004 | 30 | 360 | 360 | 0 | 1,200 | 2026-07-27T10:31:50 | 2026-07-27T10:32:02 | 402.9 | true |
| EP-1005 | 30 | 12 | 12 | 12 | 40 | 2026-07-27T11:05:09 | 2026-07-27T11:05:09 | 21.3 | true |
아래 JSON Schema에 맞는 기록 요약 행을 25건 더 만들어 주세요. {"type":"object","properties":{"episode_id":{"type":"string"},"expected_fps":{"type":"number"},"cam_top_frames":{"type":["integer","null"]},"cam_wrist_frames":{"type":["integer","null"]},"cam_side_frames":{"type":["integer","null"]},"joint_samples":{"type":["integer","null"]},"bag_start":{"type":"string"},"bag_end":{"type":"string"},"size_mb":{"type":"number"},"checksum_ok":{"type":["boolean","null"]}},"required":["episode_id","expected_fps","bag_start","bag_end","size_mb"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 카메라 세 대 가운데 한 대만 프레임 수가 크게 모자란 행, 둘째 카메라 한 대의 프레임 수가 0 인 행, 셋째 joint_samples 가 null 이라 관절 기록이 통째로 빠진 행, 넷째 bag_end 에서 bag_start 를 뺀 시간이 에피소드 재생 길이와 크게 어긋난 행, 다섯째 bag_end 가 bag_start 보다 앞선 행, 여섯째 checksum_ok 가 false 이거나 null 인 행, 일곱째 size_mb 가 재생 길이에 비해 지나치게 작거나 큰 행, 여덟째 에피소드 목록에는 없는 episode_id 가 들어온 행. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"episode_id":{"type":"string"},"expected_fps":{"type":"number"},"cam_top_frames":{"type":["integer","null"]},"cam_wrist_frames":{"type":["integer","null"]},"cam_side_frames":{"type":["integer","null"]},"joint_samples":{"type":["integer","null"]},"bag_start":{"type":"string"},"bag_end":{"type":"string"},"size_mb":{"type":"number"},"checksum_ok":{"type":["boolean","null"]}},"required":["episode_id","expected_fps","bag_start","bag_end","size_mb"]}| Python | CLI를 구현하는 주 언어입니다. 파일 읽기와 결함 판정 규칙과 요청서 생성이 모두 여기서 끝나고, 하위 명령과 옵션 해석도 같이 처리합니다. |
| C++ | 이 도구를 C++ 로 쓰지 않습니다. 고객사 수집 리그의 기록 프로그램이 C++ 로 만들어져 있어 그쪽이 남기는 요약 필드 이름과 단위에 입력 규격을 맞추는 데까지만 쓰고, 실시간 센서 파서를 새로 만드는 일은 이 PoC 범위에서 뺐습니다. |
| ROS | 입력 기록 메타의 필드 이름을 ROS2 bag 요약 내보내기 형태에 맞춰, 실제 현장에서는 수집 리그가 남긴 bag 요약을 그대로 물릴 수 있게 합니다. 토픽을 실시간으로 구독하지 않고 이미 기록이 끝난 요약 파일만 읽습니다. |
| Docker | 3주차에 도구와 규칙을 이미지로 묶어, 고객사 수집 PC 에서도 파이썬 버전 차이 없이 같은 판정이 나오게 합니다. |
| REST | 이 도구의 범위에서는 쓰지 않습니다. 입력도 파일이고 산출물도 파일이라 서버를 두지 않는 편이 현장 반입에 유리합니다. |
| gRPC | 이 도구의 범위에서는 쓰지 않습니다. 실시간 작업 스트림 연동은 수집 리그 쪽 과제라서, 이 PoC 는 수집이 끝난 뒤의 배치 검사에만 범위를 둡니다. |
| PyTorch | 3주차에 통과 판정을 받은 에피소드 목록을 Dataset 으로 여는 스모크 테스트에 씁니다. 학습을 돌리는 것이 아니라 반입 형식이 맞는지 확인하는 용도입니다. |
| Linux | 고객사 수집 PC 가 리눅스라서 경로와 권한과 남은 용량 점검을 표준출력 앞머리에 함께 찍고, 종료 코드로 다른 스크립트가 이어받게 합니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차에서 현장 회신을 물려 다시 판정하고 남은 항목만 2차 요청서로 냅니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 3주차에서 도구를 컨테이너로 묶어 수집 PC 에서 돌리고 통과분을 학습 로더로 열어 봅니다.
재수집 요청서를 이루는 확인 질문 한 건입니다. 어떤 규칙에 어떤 에피소드가 왜 걸렸는지와 누구에게 물어 어떤 조치를 고르게 할지를 함께 담아, 현장에서 이유를 물으면 담당자가 바로 근거를 답하게 합니다.
{"type":"object","properties":{"question_id":{"type":"string"},"site":{"type":"string"},"rule_id":{"type":"string","enum":["LABEL_MISSING","LABEL_UNMAPPED","SUCCESS_CONFLICT","FRAME_DROP","CAMERA_DEAD","STREAM_MISSING","CLOCK_MISMATCH","DURATION_OUTLIER","CALIB_STALE","CHECKSUM_FAIL"]},"severity":{"type":"string","enum":["blocker","recheck","note"]},"episode_ids":{"type":"array","items":{"type":"string"}},"question":{"type":"string"},"options":{"type":"array","items":{"type":"string","enum":["recollect","relabel","keep_as_is","confirm_calibration","ask_ml_team"]}},"asked_to":{"type":"string","enum":["site_operator","si_partner","internal_ml"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"value":{"type":"string"}},"required":["field","value"]}},"needs_human_review":{"type":"boolean"},"hedge":{"type":["string","null"]}},"required":["question_id","site","rule_id","severity","episode_ids","question","options","asked_to","evidence","needs_human_review"]}[
{
"question_id": "Q-001",
"site": "화성 2공장",
"rule_id": "CAMERA_DEAD",
"severity": "blocker",
"episode_ids": [
"EP-1004"
],
"question": "측면 카메라 프레임이 한 장도 기록되지 않았습니다. 촬영 중 케이블이 빠진 것인지 확인해 주시고, 같은 조건으로 다시 수집할 수 있는지 알려 주세요.",
"options": [
"recollect",
"keep_as_is"
],
"asked_to": "site_operator",
"evidence": [
{
"field": "recording_meta.cam_side_frames",
"value": "0"
},
{
"field": "recording_meta.cam_top_frames",
"value": "360"
}
],
"needs_human_review": false,
"hedge": null
},
{
"question_id": "Q-002",
"site": "화성 2공장",
"rule_id": "LABEL_UNMAPPED",
"severity": "recheck",
"episode_ids": [
"EP-1002",
"EP-1003"
],
"question": "커넥터삽입과 connector insert 가 EP-1001 의 커넥터 삽입과 같은 작업인지 확인해 주세요. 같은 작업이면 표기를 하나로 맞추겠습니다.",
"options": [
"relabel",
"keep_as_is"
],
"asked_to": "site_operator",
"evidence": [
{
"field": "episode.task_label_raw",
"value": "커넥터삽입"
},
{
"field": "episode.task_label_raw",
"value": "connector insert"
}
],
"needs_human_review": false,
"hedge": null
},
{
"question_id": "Q-003",
"site": "화성 2공장",
"rule_id": "SUCCESS_CONFLICT",
"severity": "blocker",
"episode_ids": [
"EP-1007"
],
"question": "성공으로 표기됐는데 비상정지로 중단된 기록이 함께 남아 있습니다. 둘 중 어느 쪽이 맞는지 알려 주세요.",
"options": [
"relabel",
"recollect"
],
"asked_to": "site_operator",
"evidence": [
{
"field": "episode.success_flag",
"value": "true"
},
{
"field": "episode.abort_reason",
"value": "estop"
}
],
"needs_human_review": false,
"hedge": null
},
{
"question_id": "Q-004",
"site": "평택 1공장",
"rule_id": "CLOCK_MISMATCH",
"severity": "recheck",
"episode_ids": [
"EP-1012"
],
"question": "기록 시각으로 계산한 길이는 12초인데 에피소드 재생 길이는 20.3초로 적혀 있습니다. 수집 PC 와 로봇 제어기의 시각이 맞춰져 있는지 확인해 주세요.",
"options": [
"confirm_calibration",
"recollect",
"ask_ml_team"
],
"asked_to": "si_partner",
"evidence": [
{
"field": "recording_meta.bag_start",
"value": "2026-07-25T16:02:58"
},
{
"field": "recording_meta.bag_end",
"value": "2026-07-25T16:03:10"
},
{
"field": "episode.duration_s",
"value": "20.3"
}
],
"needs_human_review": true,
"hedge": "시각 차이의 원인이 시계 어긋남인지 기록 누락인지는 파일만으로 갈라지지 않아 현장 확인이 필요합니다."
}
][자동 검증] 1. 질문 JSON 이 위 스키마를 통과하는지 확인합니다. rule_id 나 severity 나 options 가 정해진 값 밖이면 그 항목을 버립니다. 2. evidence 의 value 가 입력 파일의 실제 필드 값과 문자 그대로 일치하는지 대조합니다. 판정 근거에 지어낸 값이 붙으면 요청서 전체를 신뢰할 수 없기 때문입니다. 3. episode_ids 가 전부 에피소드 목록에 실재하는 식별자인지 확인합니다. 목록에 없는 식별자는 지어낸 것으로 보고 버립니다. 4. rule_id 별로 정해 둔 판정 조건을 입력 값으로 재계산해 같은 결론이 나오는지 확인합니다. 프레임 결손은 재생 길이와 expected_fps 의 곱 대비 부족률로, 시각 불일치는 bag_end 에서 bag_start 를 뺀 값과 duration_s 의 차이로 다시 셉니다. 5. 2주차의 자유 문장 비고에서 옮긴 조치 초안은 인용이 회신 원문에 실재하는 문자열인지 대조하고, 실재하지 않으면 확인 필요 절로 돌립니다. 6. 같은 입력으로 두 번 실행한 요청서 파일이 문자 그대로 같은지 비교합니다. 결정론이 깨지면 규칙에 시각 의존 버그가 들어간 것입니다. [수동 검증] 1. 생성된 요청서 한 부를 현장 담당자 관점에서 읽고, 그대로 보내도 답할 수 있는 질문인지 봅니다. 2. blocker 로 잡힌 항목이 정말 학습 반입을 막아야 하는 수준인지 원본 값과 대조합니다. 3. 표기 흔들림 질문이 실제로는 다른 작업인 회차를 같은 작업으로 묶지 않았는지 봅니다. 묶였다면 과탐 사례로 기록하고 사전을 다듬습니다.