피부 병변 이미지를 10개 유형으로 분류하고 Top-3 예측, 병변 정보, Grad-CAM 이미지를 제공하는 End-to-End 딥러닝 서비스
본 프로젝트의 결과는 학습 목적의 참고용 예측이며 의료적 진단을 대체하지 않습니다.
![]() |
![]() |
![]() |
![]() |
| 권영우(팀장) |
이환희 | 김승현 | 최도인 |
담당 모델EfficientNet-B0 |
담당 모델Custom CNN |
담당 모델MobileNetV3-Large |
담당 모델ResNet18 |
사용자가 JPG·JPEG·PNG 이미지를 선택하면 브라우저에서 형식과 10MB 용량 제한을 먼저 확인한 뒤 분석 API로 전송합니다.
결과 모달은 Top-1과 Top-3 Softmax 점수, 추론 시간, 모델이 참고한 영역을 나타내는 Grad-CAM, 병변별 설명·치료·생활관리·주의사항을 함께 보여줍니다.
| 항목 | 현재 결과 |
|---|---|
| 분류 대상 | 10개 피부 병변 클래스 |
| 비교 모델 | Custom CNN, ResNet18, EfficientNet-B0, MobileNetV3-Large |
| 서비스 모델 | EfficientNet-B0 |
| 입력·전처리 | RGB, 224×224, ImageNet Normalize |
| 모델 출력 | Top-1, Top-3 확률, 추론 시간, 선택적 Grad-CAM |
| 백엔드 | FastAPI, PyTorch, 서버 시작 시 checkpoint 1회 로드 |
| 프런트엔드 | Next.js 16, React 19, TypeScript |
| 배포 | Vercel 프런트엔드 + AWS EC2·Nginx·Docker 기반 CPU 추론 API |
| 외부 데이터 평가 | Accuracy 60.87%, Macro F1 0.5952, Macro Recall 0.7263 |
| 단계 | 주요 작업 | 상태 |
|---|---|---|
| 1. 기획 | 문제 정의, 공통 실험 규칙, 서비스 요구사항, 외부 비교 방안 작성 | 완료 |
| 2. 데이터 | AI-Hub 합성 데이터 재구성, 고정 split, 증강·정규화 파이프라인 구성 | 완료¹ |
| 3. 모델링 | 4개 모델을 공통 인터페이스와 설정 파일로 구현 | 완료 |
| 4. 학습 추적 | W&B로 loss, accuracy, Macro F1, learning rate 기록 | 완료 |
| 5. 평가·선정 | checkpoint 평가, confusion matrix, 외부 138장 비교, EfficientNet-B0 서비스 적용 | 완료 |
| 6. 설명 가능성 | CLI와 API에서 공유하는 Grad-CAM 구현 | 완료 |
| 7. API | /health, /predict, /lesions, 입력 검증, CORS, 오류 처리 구현 |
완료 |
| 8. 웹 | 이미지 업로드·미리보기, Top-3 결과 모달, 병변 목록 화면 구현 | 완료 |
| 9. 컨테이너 | CPU용 PyTorch 기반 FastAPI Docker 이미지와 health check 구성 | 완료 |
| 10. 배포 | skina-ai.xyz 및 api.skina-ai.xyz 공개 배포 |
완료² |
| 11. QA | 백엔드 테스트, 프런트 lint/build, 공개 health endpoint 점검 | 진행 중³ |
- 최종 학습 계약은 10클래스지만, 2026-08-26 점검 당시 이 작업 환경의
data/processed는 과거 15클래스 데이터입니다. 최종 데이터로 교체하기 전에는 재학습용--overwrite를 실행하지 마세요. - 2026-08-26 기준 프런트엔드가 HTTP 200으로 응답하고, 공개 API가 EfficientNet-B0·10클래스·CPU 모델 로드 완료 상태를 반환하는 것을 확인했습니다.
- 코드와 임시 checkpoint만 사용하는 테스트 21건은 통과했습니다. 실제 processed dataset 결합 테스트 1건은 위 15클래스 로컬 데이터 때문에 보류 상태입니다.
세부 기획과 구현 기준은 docs/skina_service_plan.md, docs/skina_service_roadmap.md, docs/skina_comparison_plan.md에서 확인할 수 있습니다.
Processed Dataset
→ 4개 모델 학습·W&B 추적
→ Accuracy·Macro F1·Confusion Matrix 평가
→ EfficientNet-B0 checkpoint
→ FastAPI InferenceModel
→ Top-3·병변 정보·선택적 Grad-CAM
→ Next.js 결과 화면
- 학습, CLI 추론, 웹 API가
src/pipeline/inference.py의 checkpoint 검증과 전처리를 공유합니다. configs/class_names.json을 클래스 이름과 순서의 단일 기준으로 사용합니다.- API 서버는 lifespan에서 모델을 한 번만 로드하고 요청마다 재사용합니다.
- Grad-CAM 실패가 핵심 Top-3 응답을 막지 않도록 별도 오류 경로로 격리했습니다.
최종 모델 학습 기준 데이터는 AI-Hub Dataset 71864 피부종양 이미지 합성 데이터를 서비스 대상 10개 클래스로 재구성한 12,000장입니다.
| Split | 이미지 수 | 클래스당 이미지 수 | 용도 |
|---|---|---|---|
| Train | 10,000 | 1,000 | 모델 학습과 augmentation |
| Validation | 1,000 | 100 | epoch별 검증과 best checkpoint 선정 |
| Test | 1,000 | 100 | 모델 선정 후 최종 평가 |
actinic_keratosis basal_cell_carcinoma
dermatofibroma hemangioma
lentigo malignant_melanoma
melanocytic_nevus seborrheic_keratosis
squamous_cell_carcinoma wart
데이터는 저장소에 포함하지 않으며 다음과 같이 클래스 폴더명을 정답 라벨로 사용하는 ImageFolder 구조로 배치합니다.
data/processed/
├── train/<class_name>/*.{jpg,jpeg,png}
├── val/<class_name>/*.{jpg,jpeg,png}
└── test/<class_name>/*.{jpg,jpeg,png}
학습에는 RandomResizedCrop(224)와 RandomHorizontalFlip()을 적용하고, validation·test·서비스 추론에는 동일한 Resize(224×224)와 ImageNet 정규화를 적용합니다. dataset, checkpoint, API 병변 정보의 클래스 수와 순서가 다르면 실행 초기에 중단합니다.
| 모델 | 구조 | Pretrained |
|---|---|---|
| Custom CNN | 3개 Conv-BN-ReLU-Pool block + Adaptive Average Pooling | 아니요 |
| ResNet18 | torchvision ResNet18의 FC를 10클래스로 교체 | ImageNet |
| EfficientNet-B0 | torchvision classifier를 10클래스로 교체 | ImageNet |
| MobileNetV3-Large | torchvision classifier를 10클래스로 교체 | ImageNet |
공통 실험은 입력 224×224, batch size 32, Adam, learning rate 1e-4, seed 42를 기준으로 하며 Validation Macro F1이 가장 높을 때 checkpoint를 저장합니다.
W&B의 두 번째 실험에서 마지막으로 기록된 8 epoch 기준 validation 결과는 다음과 같습니다.
| 모델 | Validation Accuracy | Validation Macro F1 |
|---|---|---|
| Custom CNN | 0.312 | 0.3028 |
| ResNet18 | 0.715 | 0.7140 |
| EfficientNet-B0 | 0.768 | 0.7653 |
| MobileNetV3-Large | 0.773 | 0.7723 |
내부 validation 수치는 합성·재구성 데이터에 대한 결과입니다. 실제 서비스 모델은 외부 데이터 재평가와 서비스 구성 결정을 거쳐 EfficientNet-B0로 고정했습니다.
학습 데이터 밖의 138장으로 이전 15클래스 EfficientNet-B0와 최근 10클래스 EfficientNet-B0를 동일한 전처리와 Top-1 기준으로 비교했습니다.
| 모델 | Accuracy | Macro F1 | Macro Recall | 정답 수 |
|---|---|---|---|---|
| 이전 15클래스 모델 | 21.74% | 0.1377 | 0.1694 | 30/138 |
| 최근 10클래스 모델 | 60.87% | 0.5952 | 0.7263 | 84/138 |
| 변화 | +39.13%p | +0.4575 | +0.5569 | +54장 |
- 이전 오답에서 최근 정답으로 전환된 이미지는 61장, 반대 전환은 7장입니다.
melanocytic_nevus,seborrheic_keratosis,squamous_cell_carcinoma,wart의 Recall이 크게 개선됐습니다.malignant_melanomaRecall은 0.9048에서 0.6190으로 낮아져 추가 보완이 필요합니다.- 두 checkpoint는 클래스 구성까지 달라 순수한 학습 기법 하나의 효과만 분리한 통제 실험은 아닙니다.
- 일부 클래스는 표본이 1~3장뿐이므로 Macro 지표 해석에 주의해야 합니다.
전체 수치와 이미지별 예측 결과는 outputs/external_comparison/comparison_report.md와 predictions.csv에서 확인할 수 있습니다.
| Method | Endpoint | 설명 |
|---|---|---|
GET |
/health |
모델명, 클래스 수, device, 로드 상태 확인 |
POST |
/predict |
multipart/form-data의 image를 받아 Top-3·병변 정보·Grad-CAM 반환 |
GET |
/lesions |
10개 병변의 한·영 이름과 정적 정보 반환 |
POST /predict는 JPG·JPEG·PNG, 빈 파일, 손상 이미지, 위조 MIME, 최대 업로드 크기를 검사합니다. Swagger UI는 로컬 실행 시 http://localhost:8000/docs에서 확인할 수 있습니다.
Python과 Node.js가 필요하며 Docker 이미지와 같은 Python 3.12 환경을 권장합니다. 실제 추론에는 학습된 10클래스 checkpoint를 outputs/models/efficientnet_b0_best.pth에 별도로 배치해야 합니다.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtMODEL_PATH=outputs/models/efficientnet_b0_best.pth \
ALLOWED_ORIGINS=http://localhost:3000 \
ENABLE_GRADCAM=true \
uvicorn service.api.main:app --reload --host 0.0.0.0 --port 8000curl http://localhost:8000/healthcd service/web
cp .env.example .env.local
npm install
npm run dev브라우저에서 http://localhost:3000에 접속합니다. .env.local의 NEXT_PUBLIC_API_BASE_URL은 기본적으로 http://localhost:8000을 가리킵니다.
Docker 이미지는 API만 포함하고 대용량 checkpoint는 read-only volume으로 주입합니다.
docker build -t skina-api .
docker run --rm -p 8000:8000 \
-e ALLOWED_ORIGINS=http://localhost:3000 \
-e ENABLE_GRADCAM=true \
-v "$(pwd)/outputs/models/efficientnet_b0_best.pth:/models/efficientnet_b0_best.pth:ro" \
skina-api컨테이너는 Python 3.12 slim, CPU용 PyTorch, non-root 사용자, /health 기반 health check로 구성되어 있습니다.
skina는 프런트엔드와 AI 추론 서버를 분리해 배포했습니다. Next.js 웹앱은 GitHub와 연결한 Vercel에서 제공하고, PyTorch 모델을 실행하는 FastAPI 서버는 AWS EC2 서울 리전에 Docker 컨테이너로 배포했습니다.
사용자 → Vercel(Next.js) → HTTPS API → Nginx → EC2 Docker(FastAPI·PyTorch)
| 영역 | 구성 |
|---|---|
| Frontend | Vercel · Next.js · skina-ai.xyz |
| Backend | AWS EC2 · Docker · FastAPI · api.skina-ai.xyz |
| Model serving | EfficientNet-B0 checkpoint를 EC2에 별도 보관하고 컨테이너 시작 시 1회 로드 |
| Network | Nginx reverse proxy와 HTTPS를 적용하고 서비스 도메인만 CORS 허용 |
프런트엔드는 main 브랜치 변경 시 Vercel에서 자동으로 다시 빌드됩니다. 백엔드는 학습 환경과 운영 환경의 차이를 줄이기 위해 Python·PyTorch·API 코드를 Docker 이미지로 관리하고, 용량이 큰 모델 checkpoint는 이미지에 포함하지 않고 별도로 연결했습니다. 이를 통해 웹 UI와 모델 서버를 독립적으로 업데이트하면서도 동일한 추론 환경을 유지할 수 있도록 구성했습니다.
최종 10클래스 원본과 split 규칙을 확인한 뒤에만 데이터를 준비합니다. 기존 processed 데이터가 있으면 명령이 안전하게 중단됩니다.
python -m src.data.prepare_data의도적으로 같은 규칙으로 다시 생성할 때만 --overwrite를 사용합니다.
python -m src.data.prepare_data --overwrite모델별 학습과 best checkpoint 평가는 다음과 같이 실행합니다.
python -m src.pipeline.train --config configs/cnn.json
python -m src.pipeline.train --config configs/resnet18.json
python -m src.pipeline.train --config configs/efficientnet_b0.json
python -m src.pipeline.train --config configs/mobilenet_v3.json
python -m src.pipeline.evaluate --config configs/efficientnet_b0.json단일 이미지 추론과 Grad-CAM은 같은 checkpoint 로더와 전처리를 사용합니다.
python -m src.pipeline.predict \
--config configs/efficientnet_b0.json \
--image sample/basal_cell_carcinoma_sample1.png
python -m src.pipeline.gradcam \
--config configs/efficientnet_b0.json \
--image sample/basal_cell_carcinoma_sample1.pngpython -m unittest discover -v
cd service/web
npm run lint
npm run build2026-08-26 점검 결과:
- 백엔드·추론·API 테스트 21건 통과
- 데이터 클래스 결합 테스트 1건 보류: 로컬 processed 데이터가 과거 15클래스 구성
- ESLint 통과
- Next.js production build 통과:
/,/lesions,/opengraph-image정적 생성 - 공개 프런트엔드 HTTP 200 확인
- 공개 API
/health에서 EfficientNet-B0, 10 classes, CPU, model loaded 확인
skina/
├── configs/ # 모델별 학습·추론 설정과 클래스 기준
├── data/ # Git 제외 원본·processed 데이터
├── docs/ # 기획, 실험 규칙, 서비스 로드맵
├── notebooks/01_eda.ipynb # 데이터 EDA
├── outputs/
│ ├── external_comparison/ # 외부 평가 지표·그래프·예측 결과
│ ├── models/ # Git 제외 best checkpoints
│ ├── plots/ # confusion matrix·Grad-CAM
│ └── results/ # test metrics JSON
├── readme_image/ # README용 서비스·아키텍처·W&B 이미지
├── sample/ # CLI 확인용 샘플 이미지
├── service/
│ ├── api/ # FastAPI, 병변 정보, 요청·응답 schema
│ └── web/ # Next.js App Router 웹앱
├── src/
│ ├── data/ # split, transform, DataLoader
│ ├── models/ # 4개 분류 모델
│ └── pipeline/ # train, evaluate, inference, Grad-CAM
├── tests/ # API·추론·클래스 계약 테스트
├── Dockerfile
├── requirements.txt
└── README.md
피부 병변이 의심되거나 변화·출혈·통증이 지속되면 AI 예측에 의존하지 말고 의료 전문가의 진료를 받으세요.







.png)


