레브잇
경력근거: 이미지 한 장만으로 사용자가 원하는 상품을 발견할 수 있는 이미지 검색 렌즈를 만들고 있습니다
※ 실제 산출물이 아닌 AI 예측 시안입니다.
레브잇 PMF팀은 쇼포트에서 이미지 한 장으로 상품을 찾는 검색 렌즈를 만들고, 공고는 수천만 개의 상품 이미지와 사용자 검색 데이터로 그 유사도 검색의 품질을 직접 끌어올리는 일을 맡깁니다. 어긋난 이유를 카테고리·색·카탈로그 존재로 나눠 두면 다음 실험에서 무엇을 고칠지가 임베딩 점수 하나보다 빨리 정해지고, 이 판정은 패션·뷰티·건강식품·가전·가구가 한 카탈로그에 섞여 있는 쇼포트의 상품 구성이 있어야 성립합니다. 다만 이 포트폴리오는 검색 결과를 정답 라벨과 견주는 규칙과 그 결과를 읽는 화면을 보여줍니다. 임베딩 모델 학습이나 대규모 벡터 검색 인프라 운영 전문성은 주장하지 않습니다. 어려운 짝을 어떻게 골라 손실을 짰느냐고 물으시면 3주차에 처음 해 보는 일이라고 답하겠습니다.
공고는 사용자가 올린 사진에서 상품 영역을 잡아 임베딩으로 바꾸고 수천만 장 규모의 상품 이미지 벡터에서 비슷한 상품을 찾는 렌즈를 팀의 만들고 있는 물건으로 적었습니다. 그 렌즈가 올린 결과를 판정 대상으로 잡았습니다.
모델의 검색 결과와 실패 사례를 분석해 원인을 파악하고 개선 방법을 찾아내는 사람을 찾는다고 적혀 있습니다. 실패를 다섯 갈래로 나눠 두는 화면이 그 분석의 첫 도구입니다.
모델 성능 개선이 실제 사용자 경험과 제품 지표 향상으로 이어지는지 Problem Solver 와 검증한다고 적었습니다. 판정 분포가 학습 전후로 어떻게 움직였는지가 그 대화의 재료가 됩니다.
질의 사진 한 장에 렌즈가 올린 결과 목록과 그 질의의 정답 라벨을 나란히 놓고, 결과 한 줄마다 정답 상품과 카테고리·색이 어디서 어긋나는지 정합니다. 상품 번호가 카탈로그에 서 있지 않으면 그것부터 짚고, 정답 상품이 상위 다섯 자리 안에 아예 없으면 그 질의의 첫 줄에서 바로 알립니다. 유사도 점수만 늘어놓으면 다 그럴듯해 보이던 결과가 다섯 판정으로 나뉘어, 오늘 어느 질의를 다시 볼지가 정해집니다.
쇼포트에서 이미지 검색 결과를 살펴 렌즈 품질을 챙기는 카탈로그 운영 담당자
유사도 점수 순서만 보면 어긋난 결과가 왜 위로 올라왔는지 알 수 없습니다. 결과마다 카탈로그 속성을 붙여 정답 라벨과 견주고 정답 상품이 몇 번째에 올라왔는지 세면, 다시 볼 질의가 다섯 갈래로 나뉘고 다음 실험에서 무엇을 만질지가 정해집니다.
검색 결과와 상품 카탈로그, 정답 라벨을 올리면 결과 한 줄마다 다섯 판정 중 하나가 붙고 판정별로 묶여 나오는 관리자 페이지입니다. 임베딩 모델은 공개 사전학습 가중치를 그대로 쓰는 것으로 시작하고, 질의 사진과 정답 라벨은 공개 상품 이미지로 지원자가 직접 만들어 넣습니다.
결과 240건에 손으로 매긴 정답 판정을 붙여 자동 판정과 대조하고 판정별 일치율과 어긋난 결과 목록을 냅니다. 질의마다 정답이 몇 번째에 올라왔는지 모아 상위 다섯 자리 적중률을 냅니다.
카테고리 어긋남과 색이 다름으로 걸린 짝을 어려운 짝으로 모아 임베딩을 다시 학습하고, 학습 전후의 판정 분포를 나란히 놓아 무엇이 줄고 무엇이 남았는지 보여주는 단계입니다.
카탈로그 운영 담당자가 검색 결과와 상품 카탈로그, 정답 라벨을 올리면 결과마다 판정이 붙고 다시 볼 질의가 판정별로 묶여 화면에 나오는 과정을 시연합니다.
결과 240건에 손으로 매긴 정답 판정을 붙여 대조하고 판정별 일치율을 냅니다. 단, 질의 사진도 정답 라벨도 제가 공개 상품 이미지로 지어낸 것이라 이 값은 렌즈 품질이 아니라 판정 차례가 제 기준대로 도는지만 말합니다.
망가진 입력 30종을 먼저 넣어 봅니다. 렌즈가 카테고리를 남기지 못했거나 카탈로그의 색 값이 비었거나 카탈로그에 서 있지 않은 상품 번호가 올라와도 화면이 서고, 견주지 못한 조건은 판정 자리 대신 hedge 로 내려갑니다.
상위 몇 자리까지 볼지를 5에서 10으로 넓혔을 때 정답을 못 올린 질의가 몇 건 줄어드는지 회귀로 확인합니다.
여기서부터는 재지 않았습니다. 위 수치는 제가 만든 검색 결과에 제가 붙인 정답 라벨로 낸 값이고, 렌즈 품질이 실제로 올랐는지는 레브잇 쇼포트의 실 검색 로그와 실 카탈로그를 넣어야 나옵니다. 그 측정을 붙이자마자 돌릴 수 있게 평가 러너와 라벨 서식을 미리 갖춰 둡니다.
판정 일치율은 제가 만든 결과를 제가 채점한 값이라 성능 근거가 못 됩니다. 면접에서는 화면이 도는 범위와, 실 검색 로그가 오면 바로 재볼 수 있다는 준비 상태까지만 말합니다.
정답 라벨도 카탈로그도 제가 적었으니 맞아떨어지는 것이 당연합니다. 색 값이 없는 상품, 렌즈가 카테고리를 남기지 못한 결과, 카탈로그에서 내려간 상품 번호를 일부러 섞어 두어 깨끗한 결과만 통과시키는 화면이 되지 않게 했습니다.
이 포트폴리오는 검색 결과를 정답 라벨과 견줘 판정으로 나누는 규칙과 화면을 보여줍니다. 임베딩 모델 구조나 벡터 검색 운영의 깊이는 주장하지 않습니다. 판정은 코드 비교와 순위 세기가 하고 모델은 담당자가 읽을 문장만 씁니다.
카테고리와 색이 같다고 해서 사용자가 같은 물건으로 받아들인다는 뜻은 아닙니다. 형태와 크기가 다른 소파도 같은 소파군으로 묶이므로, 같은 물건 판정은 확정이 아니라 담당자가 사진을 열어 볼 후보로 냅니다.
실제 카탈로그는 색 표기가 상품마다 제각각이고 카테고리 체계도 계속 바뀝니다. 표기를 표준 색군으로 잇는 사전과 카테고리 개편 이력을 붙이는 일은 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
질의 사진 한 장에 이미지 검색 렌즈가 올린 결과 목록입니다. 판정의 대상 표이며, 렌즈가 카테고리를 남기지 못한 결과와 카탈로그에서 내려간 상품 번호를 일부러 담았습니다.
| result_no | query_id | rank | product_id | similarity | detected_category |
|---|---|---|---|---|---|
| LR-4821-01 | Q-4821 | 1 | P-51002 | 0.91 | FURN-SOFA |
| LR-4821-02 | Q-4821 | 2 | P-51033 | 0.88 | FURN-SOFA |
| LR-4821-03 | Q-4821 | 3 | P-51078 | 0.84 | FURN-SOFA |
| LR-4821-04 | Q-4821 | 4 | P-51255 | 0.8 | FURN-CHAIR |
| LR-4907-01 | Q-4907 | 1 | P-51204 | 0.86 | APPL-COFFEE |
아래 JSON Schema 에 맞는 이미지 검색 결과 행을 더 만들어 주세요. {"type":"object","properties":{"result_no":{"type":"string"},"query_id":{"type":"string"},"rank":{"type":"integer"},"product_id":{"type":"string"},"similarity":{"type":"number","minimum":0,"maximum":1},"detected_category":{"type":["string","null"]}},"required":["result_no","query_id","rank","product_id","similarity","detected_category"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) detected_category 가 null 인 탐지 실패 결과, (2) product_catalog 에 없는 상품 번호가 상위에 올라온 결과, (3) 정답 상품이 rank 6 이상에서야 나오는 질의, (4) 같은 질의 안에서 유사도가 0.6 아래로 내려가는 꼬리 결과. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"result_no":{"type":"string"},"query_id":{"type":"string"},"rank":{"type":"integer"},"product_id":{"type":"string"},"similarity":{"type":"number","minimum":0,"maximum":1},"detected_category":{"type":["string","null"]}},"required":["result_no","query_id","rank","product_id","similarity","detected_category"]}결과의 상품 번호로 찾아 붙이는 상품 속성 표입니다. 카테고리 코드와 색이 판정의 잣대가 되며, 색이 미입력인 상품을 담아 색 비교를 건너뛰는 경로를 보여줍니다.
| product_id | category_code | color | material | price |
|---|---|---|---|---|
| P-51002 | FURN-SOFA | 아이보리 | 린넨 | 890,000 |
| P-51033 | FURN-SOFA | 차콜 | 패브릭 | 740,000 |
| P-51044 | FURN-SOFA | 그레이 | 부클레 | 1,020,000 |
| P-51071 | FURN-SOFA | — | 벨벳 | 880,000 |
| P-51078 | FURN-TABLE | 월넛 | 원목 | 320,000 |
아래 JSON Schema 에 맞는 상품 카탈로그 행을 더 만들어 주세요. {"type":"object","properties":{"product_id":{"type":"string"},"category_code":{"type":"string","enum":["FURN-SOFA","FURN-TABLE","FURN-CHAIR","APPL-KETTLE","APPL-COFFEE","FASH-COAT","FASH-JACKET"]},"color":{"type":["string","null"]},"material":{"type":["string","null"]},"price":{"type":"integer"}},"required":["product_id","category_code","color","material","price"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) color 가 null 인 색 미입력 상품, (2) material 이 null 인 소재 미입력 상품, (3) 같은 카테고리 안에서 색만 다른 상품 두세 벌, (4) 이름은 비슷한데 카테고리 코드가 다른 상품. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"product_id":{"type":"string"},"category_code":{"type":"string","enum":["FURN-SOFA","FURN-TABLE","FURN-CHAIR","APPL-KETTLE","APPL-COFFEE","FASH-COAT","FASH-JACKET"]},"color":{"type":["string","null"]},"material":{"type":["string","null"]},"price":{"type":"integer"}},"required":["product_id","category_code","color","material","price"]}질의 사진마다 사람이 붙인 정답 상품과 정답 카테고리입니다. 판정의 기준 표이며, 결과 목록보다 많은 질의를 담아 아직 결과가 쌓이지 않은 질의도 그대로 둡니다. 그 질의들은 판정할 결과 행이 없어 verdict 가 하나도 안 만들어지므로, 판정 묶음이 아니라 화면 위쪽의 결과 없음 줄에서 셉니다.
| query_id | true_product_id | true_category_code | labeled_at |
|---|---|---|---|
| Q-4821 | P-51002 | FURN-SOFA | 2026-08-31 |
| Q-4907 | P-51188 | APPL-KETTLE | 2026-08-31 |
| Q-5033 | P-51044 | FURN-SOFA | 2026-09-01 |
| Q-5140 | P-51301 | FASH-COAT | 2026-09-01 |
| Q-5211 | P-51377 | FURN-TABLE | 2026-09-02 |
아래 JSON Schema 에 맞는 질의 정답 라벨 행을 더 만들어 주세요. {"type":"object","properties":{"query_id":{"type":"string"},"true_product_id":{"type":"string"},"true_category_code":{"type":"string","enum":["FURN-SOFA","FURN-TABLE","FURN-CHAIR","APPL-KETTLE","APPL-COFFEE","FASH-COAT","FASH-JACKET"]},"labeled_at":{"type":"string"}},"required":["query_id","true_product_id","true_category_code","labeled_at"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 정답 상품이 lens_results 상위에 한 번도 안 나오는 질의, (2) 같은 카테고리에 정답 후보가 여럿인 질의, (3) 라벨을 붙인 날짜가 결과 수집일보다 늦은 질의. true_product_id 는 product_catalog 에 실재하는 번호로만 만들고, 생성한 행은 반드시 위 스키마를 통과해야 합니다.{"type":"object","properties":{"query_id":{"type":"string"},"true_product_id":{"type":"string"},"true_category_code":{"type":"string","enum":["FURN-SOFA","FURN-TABLE","FURN-CHAIR","APPL-KETTLE","APPL-COFFEE","FASH-COAT","FASH-JACKET"]},"labeled_at":{"type":"string"}},"required":["query_id","true_product_id","true_category_code","labeled_at"]}| Computer Vision | 질의 사진에서 상품 영역을 잘라 내고 그 조각이 무슨 카테고리인지 정하는 탐지 모델입니다. 렌즈가 무엇을 보고 결과를 올렸는지 detected_category 로 남겨 판정의 hedge 가 됩니다. |
| Image Embedding Model | 잘라 낸 조각과 카탈로그 상품 이미지를 같은 벡터 공간에 놓아 유사도 점수를 냅니다. 1주차는 공개 사전학습 가중치를 그대로 쓰고 학습은 3주차에 만집니다. |
| Representation Learning | 3주차에 카테고리·색이 어긋난 짝만 모아 어려운 짝으로 다시 학습해 임베딩을 다듬는 자리입니다. |
| Vector Search | 카탈로그 벡터에서 상위 결과를 뽑는 검색기입니다. 상위 몇 자리까지 볼지가 정답을 못 올림 판정의 기준선이 됩니다. |
| Ranking | 유사도 점수 순서를 rank 로 굳혀, 질의마다 정답이 몇 번째에 올라왔는지 셀 수 있게 합니다. |
| Python | 판정 규칙 엔진과 평가 러너의 언어입니다. 카테고리·색 비교와 상위 자리 계산이 여기 있습니다. |
| Next.js | 판정별로 묶은 목록과 질의 하나를 펼쳐 보는 상세를 그리는 관리자 페이지입니다. |
| LLM | 판정이 정해진 뒤 어긋난 상품을 담당자가 읽을 한 문장으로 적습니다. |
✗ fail → 라벨 없는 질의나 카탈로그에 없는 상품 번호가 절반을 넘으면 색인을 다시 만든 뒤 라벨부터 채웁니다.
✓ pass → 2주차 정답 대조로 넘어갑니다.
✗ fail → 어긋난 결과를 모아 판정 차례와 색 비교 기준을 다시 봅니다.
✓ pass → 3주차 어려운 짝 재학습으로 넘어갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · result_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
같은 물건정답과 같은 물건카테고리 어긋남카테고리 어긋남색이 다름색 다름카탈로그에 없음카탈로그 없음정답을 못 올림상위 다섯 자리 밖질의 사진 한 장에 올라온 결과 한 줄이 정답 라벨과 견줘 어떤 판정을 받는지 남기는 형식입니다. 판정은 규칙이 정하고 요약 문장만 모델이 씁니다.
{"type":"object","properties":{"verdict_no":{"type":"string"},"result_no":{"type":"string"},"query_id":{"type":"string"},"product_id":{"type":"string"},"verdict":{"type":"string","enum":["같은 물건","카테고리 어긋남","색이 다름","카탈로그에 없음","정답을 못 올림"]},"answer_rank":{"type":["integer","null"],"description":"그 질의의 lens_results 를 rank 오름차순으로 훑어 query_labels.true_product_id 와 같은 product_id 가 처음 나오는 rank 입니다. 올라온 결과 어디에도 없으면 비웁니다."},"answer_in_top5":{"type":"boolean","description":"answer_rank 가 1 이상 5 이하이면 참이고, 비었거나 6 이상이면 거짓입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["verdict_no","result_no","query_id","product_id","verdict","answer_rank","answer_in_top5","evidence","summary"]}[
{
"verdict_no": "VR-0001",
"result_no": "LR-4821-01",
"query_id": "Q-4821",
"product_id": "P-51002",
"verdict": "같은 물건",
"answer_rank": 1,
"answer_in_top5": true,
"evidence": [
{
"field": "product_id",
"value": "P-51002"
},
{
"field": "true_product_id",
"value": "P-51002"
},
{
"field": "category_code",
"value": "FURN-SOFA"
},
{
"field": "similarity",
"value": 0.91
}
],
"summary": "첫 자리에 올라온 린넨 3인 소파가 정답 상품 그대로입니다.",
"hedge": null
},
{
"verdict_no": "VR-0002",
"result_no": "LR-4821-02",
"query_id": "Q-4821",
"product_id": "P-51033",
"verdict": "색이 다름",
"answer_rank": 1,
"answer_in_top5": true,
"evidence": [
{
"field": "product_id",
"value": "P-51033"
},
{
"field": "true_product_id",
"value": "P-51002"
},
{
"field": "category_code",
"value": "FURN-SOFA"
},
{
"field": "color",
"value": "차콜"
},
{
"field": "color",
"value": "아이보리"
}
],
"summary": "같은 소파인데 차콜이 올라와 정답 상품의 아이보리와 색이 다릅니다.",
"hedge": null
},
{
"verdict_no": "VR-0003",
"result_no": "LR-4821-03",
"query_id": "Q-4821",
"product_id": "P-51078",
"verdict": "카테고리 어긋남",
"answer_rank": 1,
"answer_in_top5": true,
"evidence": [
{
"field": "product_id",
"value": "P-51078"
},
{
"field": "category_code",
"value": "FURN-TABLE"
},
{
"field": "true_category_code",
"value": "FURN-SOFA"
},
{
"field": "detected_category",
"value": "FURN-SOFA"
}
],
"summary": "라벨은 소파인데 원목 사이드테이블이 셋째 자리에 올라왔습니다.",
"hedge": "렌즈가 사진 조각을 소파로 봤다고 남긴 값이라 자른 영역이 소파 팔걸이였을 가능성이 있습니다."
},
{
"verdict_no": "VR-0004",
"result_no": "LR-4907-01",
"query_id": "Q-4907",
"product_id": "P-51204",
"verdict": "정답을 못 올림",
"answer_rank": 12,
"answer_in_top5": false,
"evidence": [
{
"field": "true_product_id",
"value": "P-51188"
},
{
"field": "rank",
"value": 12
},
{
"field": "product_id",
"value": "P-51204"
},
{
"field": "similarity",
"value": 0.86
}
],
"summary": "정답 주전자는 열두 번째에야 올라오고 첫 자리는 커피메이커가 차지했습니다.",
"hedge": null
},
{
"verdict_no": "VR-0005",
"result_no": "LR-5033-02",
"query_id": "Q-5033",
"product_id": "P-51071",
"verdict": "같은 물건",
"answer_rank": 1,
"answer_in_top5": true,
"evidence": [
{
"field": "product_id",
"value": "P-51071"
},
{
"field": "category_code",
"value": "FURN-SOFA"
},
{
"field": "true_category_code",
"value": "FURN-SOFA"
},
{
"field": "color",
"value": null
},
{
"field": "detected_category",
"value": null
}
],
"summary": "정답과 같은 소파군이라 둘째 자리 결과를 그대로 둡니다.",
"hedge": "카탈로그의 색 값이 비어 있어 색 비교를 하지 않았고, 렌즈도 카테고리를 남기지 못해 근거가 한 칸 모자랍니다."
},
{
"verdict_no": "VR-0006",
"result_no": "LR-5033-03",
"query_id": "Q-5033",
"product_id": "P-59901",
"verdict": "카탈로그에 없음",
"answer_rank": 1,
"answer_in_top5": true,
"evidence": [
{
"field": "product_id",
"value": "P-59901"
},
{
"field": "detected_category",
"value": "FURN-CHAIR"
},
{
"field": "similarity",
"value": 0.79
}
],
"summary": "셋째 자리 상품 번호가 지금 카탈로그에 서 있지 않아 속성을 견줄 수 없습니다.",
"hedge": "내린 상품이 색인에 남아 있을 때 이렇게 걸리므로 색인을 다시 만든 뒤 같은 번호가 또 나오는지 봐야 합니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 다섯 값 중 하나인지 확인합니다. 규칙이 정한 값이라 모델이 새 값을 지어내면 그 응답을 버립니다. 2. verdict_no·result_no·query_id·verdict·answer_rank·answer_in_top5·evidence·summary 필드가 빠짐없이 존재하는지 확인합니다. 3. similarity 가 0 이상 1 이하 범위 안인지 확인합니다. 4. 카테고리 어긋남은 그 결과의 product_id 로 찾은 product_catalog.category_code 를 query_labels.true_category_code 와 견줘 다른지 다시 계산합니다. 5. 색이 다름은 category_code 가 같은 상태에서 product_catalog.color 를 정답 상품의 color 와 견줘 다른지 다시 계산합니다. 다만 두 색 중 하나가 비어 있으면 색 비교를 건너뛰고 그 사실을 hedge 에 남깁니다. 6. 카탈로그에 없음은 그 product_id 가 product_catalog.product_id 어디에도 없는지 다시 계산합니다. 7. 정답을 못 올림은 rank 1 결과에서 query_labels.true_product_id 가 그 질의의 rank 1~5 product_id 안에 없는지 다시 계산하고, answer_rank 가 정답 상품이 처음 나온 rank 와 같은지 함께 셉니다. 8. evidence 의 field 이름이 입력에 있는 컬럼인지 대조합니다. 없는 이름을 인용하면 요약이 지어낸 문장이라는 뜻이라 규칙이 만든 기본 문장으로 되돌립니다. 9. evidence 의 value 가 수록된 행의 원문 값과 같은지 확인합니다. 10. summary 가 존댓말 한 문장으로 끝나는지 확인합니다. 11. query_labels 에 있는데 lens_results 에 결과가 한 줄도 없는 질의를 셉니다. 이런 질의는 판정할 결과 행이 없어 verdict 가 하나도 안 만들어지므로 판정 묶음에서는 보이지 않습니다. 질의 번호를 따로 모아 화면 위쪽에 결과 없음 줄로 세워, 검색이 아예 안 돈 질의가 목록에서 사라지지 않게 합니다. 샘플에서는 Q-5266·Q-5310·Q-5348·Q-5417 네 건이 여기 해당하고, 결과가 실린 여섯 질의는 해당하지 않습니다. [평가 러너 · 2주차] 손으로 매긴 정답 판정 240건과 자동 판정을 대조해 판정별 일치율을 내고, 어긋난 결과는 따로 모아 판정 차례를 손볼지 정합니다. 상위 몇 자리까지 볼지를 5와 10으로 바꿔 정답을 못 올린 질의 수가 어떻게 달라지는지도 이 러너에서 잽니다. [수동 검증 · 사람이 봅니다] 1. 색이 다름으로 걸린 결과 20건을 열어 사람 눈에도 다른 색인지 봅니다. 2. 카테고리 어긋남 중에 사용자가 그래도 받아들일 결과가 있는지 판단해 판정 차례를 다시 봅니다.