Pointcept 실전 가이드
딥러닝 지식이 없는 Python 개발자가 3D 포인트 클라우드 AI 모델을 학습·평가·추론하기까지
013분 요약
Pointcept은 “파이썬 dict로 된 설정 파일 하나를 골라서 셸 스크립트로 실행하면 3D 포인트 클라우드 AI 모델이 학습·평가되는” 프레임워크입니다. 딥러닝 코드를 쓸 일은 거의 없고, 실제로 하는 일은 설정 파일의 값을 고치는 것입니다.
전체 흐름은 딱 4단계
# 1) 설치 (conda 또는 docker) — 한 번만
conda env create -f environment.yml && conda activate pointcept-torch2.5.0-cu12.4
# 2) 데이터를 정해진 폴더 구조의 .npy 파일로 변환 → data/ 아래에 배치
python pointcept/datasets/preprocessing/s3dis/preprocess_s3dis.py \
--splits Area_1 Area_2 --dataset_root /raw/s3dis --output_root data/s3dis
# 3) 학습 — configs/scannet/semseg-pt-v3m1-0-base.py 라는 "설정 파일" 하나를 지정
sh scripts/train.sh -g 1 -d scannet -c semseg-pt-v3m1-0-base -n my-first-run
# 4) 테스트(정밀 평가) — 학습이 만든 실험 폴더를 그대로 가리킴
sh scripts/test.sh -g 1 -d scannet -n my-first-run -w model_best
기억해야 할 3개의 개념
Config (설정 파일)
configs/<데이터셋>/<작업>-<모델>-<버전>-<변형>.py. 순수 파이썬 dict입니다. 모델 구조·데이터 경로·학습률·증강 파이프라인이 전부 여기 들어 있습니다.
Registry (이름 → 클래스)
dict 안의 type="PT-v3m1" 같은 문자열이 실제 파이썬 클래스로 자동 연결됩니다. import 문을 쓸 필요가 없습니다.
exp 폴더 (실험 결과)
실행하면 exp/<데이터셋>/<실험이름>/이 생기고 로그·체크포인트·설정 사본·예측 결과가 전부 그 안에 모입니다.
02Pointcept으로 할 수 있는 일 카탈로그
2.1 작업(Task) 종류
“이 프레임워크로 뭘 할 수 있나”에 대한 답입니다. 각 작업은 설정 파일 이름의 접두어로 구분됩니다.
| 접두어 | 작업 | 입력 → 출력 | 실무 용도 예시 | 대표 설정 파일 |
|---|---|---|---|---|
semseg- | Semantic Segmentation (의미 분할) | 점 N개 → 점마다 클래스 번호 N개 | 실내 스캔에서 벽/바닥/문/창문 자동 라벨링, 자율주행 LiDAR에서 도로/차량/보행자 구분 | configs/scannet/semseg-pt-v3m1-0-base.py |
insseg- | Instance Segmentation (객체 분할) | 점 N개 → 개별 물체 ID + 클래스 + 점수 | “의자가 몇 개 있고 각각 어느 점들인가” — 재고 카운팅, 로봇 파지 대상 분리 | configs/scannet/insseg-pointgroup-v1m1-0-spunet-base.py |
cls- | Classification (형상 분류) | 점 N개(물체 하나) → 클래스 1개 | 스캔된 부품이 볼트인지 너트인지 판별 | configs/modelnet40/cls-ptv3-v1m1-0-base.py |
| (part seg) | Part Segmentation (부위 분할) | 물체 1개의 점 → 부위 라벨 | 의자의 다리/등받이/좌판 분리, 조작 가능 부위 탐지 | ShapeNetPart / PartNetE 데이터셋 + ShapeNetPartSegTester |
pretrain- | Self-supervised Pre-training (라벨 없는 사전학습) | 라벨 없는 점 → 범용 특징 추출기 | 라벨링 예산이 적을 때: 라벨 없는 스캔 수만 장으로 먼저 학습 → 소량 라벨로 미세조정 | configs/scannet/pretrain-msc-v1m1-0-spunet-base.py, configs/sonata/, configs/concerto/, configs/utonia/ |
-ppt- | Multi-dataset 공동학습 (Point Prompt Training) | 서로 라벨 체계가 다른 여러 데이터셋 동시 학습 | 실내 3종 + 실외 3종을 한 모델로. 데이터가 부족한 도메인의 성능을 올릴 때 | configs/scannet/semseg-ppt-v1m1-0-sc-st-spunet.py |
| — | Feature Extraction (임베딩 추출) | 점 → 점마다 고차원 벡터 | 유사 장면 검색, 클러스터링, 다른 파이프라인의 입력 피처. 10장 참고 | DefaultSegmentorV2(..., return_point=True) |
| — | Fine-tuning / LoRA | 사전학습 가중치 + 내 소량 데이터 | 공개 사전학습 모델을 내 도메인(공장·병원 스캔)에 적응 | configs/scannet/semseg-spunet-v1m1-4-ft.py, DefaultLORASegmentorV2 |
| — | 벤치마크 제출 파일 생성 | 테스트셋 예측 → 공식 제출 포맷 | ScanNet / SemanticKITTI / nuScenes / Waymo 리더보드 제출 | *-submit.py 설정, tools/create_waymo_semseg_submission.py |
| — | 속도 프로파일링 | 모델 실행 시간/메모리 측정 | 배포 전 성능 검토 | configs/scannet/semseg-spunet-v1m1-3-enable-profiler.py |
| — | 데이터 효율 실험 | 라벨 1%/5%/20%만으로 학습 | “라벨을 얼마나 만들어야 하나” 사전 검증 | semseg-spunet-v1m1-2-efficient-la20.py 등 |
semseg-pt-v3m1-0-base부터 시작하세요. PTv3는 이 저장소에서 실내·실외 모두 가장 성능이 좋은 기본 백본입니다.2.2 지원 모델 (백본)
백본(backbone)은 “점 → 특징 벡터”를 만드는 본체 신경망입니다. 설정 파일의 model.backbone.type 문자열만 바꾸면 교체됩니다.
type 문자열 | 모델 | 특징 / 언제 쓰나 | 추가 설치 |
|---|---|---|---|
PT-v3m1 | Point Transformer V3 | 기본 추천. 정확도·속도 모두 최상급. 실내/실외 공통 | flash-attn (선택, 없으면 enable_flash=False) |
PT-v3m2 / PT-v3m3 | PTv3 (Sonata / Utonia 변형) | 사전학습 프레임워크 전용 변형 | 동일 |
SpUNet-v1m1 | Sparse UNet (SpConv) | 가볍고 안정적. 메모리 여유가 적을 때 / 베이스라인 | spconv-cu124 |
SpUNet-v1m3 | SpUNet + PDNorm | 여러 데이터셋 공동학습(PPT)용 | 동일 |
MinkUNet34C 등 | MinkowskiEngine UNet | 논문 재현용. 설치가 까다로워 SpUNet 권장 | MinkowskiEngine |
PT-v2m2 | Point Transformer V2 | PTv3 이전 세대. 24GB GPU에서 동작 | libs/pointops |
PointTransformer-Seg50 | Point Transformer V1 | 역사적 베이스라인 | libs/pointops |
OACNNs | OA-CNNs | 희소 CNN 계열, 실내 분할에 강함 | spconv |
SPVCNN | SPVCNN | 실외 LiDAR (SemanticKITTI) | torchsparse |
OctFormer-v1m1 | OctFormer | 옥트리 기반 트랜스포머 | ocnn-pytorch, dwconv |
ST-v1m2 | Stratified Transformer | — | torch-points3d 계열 |
Swin3D-v1m1 | Swin3D | Structured3D 사전학습 조합에 강함 | MinkowskiEngine + Swin3D (기본 __init__.py에서 주석 처리됨) |
LitePT-v1 | LitePT | 경량 모델. 추론 비용 민감할 때 | — |
PG-v1m1 / SGIFormer-v1m1 | PointGroup / SGIFormer | 인스턴스 분할 전용 | libs/pointgroup_ops |
MSC-v1m1, Sonata-v1m1, Concerto-v1m1, Utonia-v1m1, PPT-v1m1 | 사전학습 프레임워크 | 백본을 감싸서 라벨 없이 학습시키는 “래퍼” | — |
MinkUNet*과 Swin3D-v1m1은 기본 상태에서 import가 주석 처리되어 있습니다. 쓰려면 pointcept/models/__init__.py와 pointcept/models/sparse_unet/__init__.py의 해당 줄 주석을 풀고 MinkowskiEngine을 설치해야 합니다. 처음이라면 PT-v3m1 또는 SpUNet-v1m1만 쓰는 편이 훨씬 편합니다.2.3 지원 데이터셋
| 데이터셋 | 종류 | dataset_type | configs 폴더 | 전처리 스크립트 |
|---|---|---|---|---|
| ScanNet v2 | 실내 RGB-D 스캔 (20 클래스) | ScanNetDataset | configs/scannet/ (43개) | preprocessing/scannet/preprocess_scannet.py |
| ScanNet200 | 실내, 200 클래스 (long-tail) | ScanNet200Dataset | configs/scannet200/ (15개) | 동일 |
| ScanNet++ | 고해상도 실내 스캔 | ScanNetPPDataset | configs/scannetpp/ (18개) | preprocessing/scannetpp/ |
| S3DIS | 실내 6개 구역 (13 클래스) | S3DISDataset | configs/s3dis/ (18개) | preprocessing/s3dis/preprocess_s3dis.py |
| Structured3D | 합성 실내 (사전학습용 대량 데이터) | Structured3DDataset | configs/structured3d/ (7개) | preprocessing/structured3d/ |
| Matterport3D / HM3D | 대규모 실내 | HM3DDataset | configs/matterport3d/ | preprocessing/matterport3d/, preprocessing/hm3d/ |
| SemanticKITTI | 실외 LiDAR (자율주행) | SemanticKITTIDataset | configs/semantic_kitti/ (8개) | 불필요 (원본 직접 로드) |
| nuScenes | 실외 LiDAR | NuScenesDataset | configs/nuscenes/ (8개) | preprocessing/nuscenes/ |
| Waymo | 실외 LiDAR | WaymoDataset | configs/waymo/ (3개) | preprocessing/waymo/ |
| ModelNet40 | 단일 물체 형상 분류 (40 클래스) | ModelNetDataset | configs/modelnet40/ (2개) | 불필요 |
| ScanObjectNN | 실제 스캔 물체 분류 | ScanObjectNNDataset | — | 불필요 |
| ShapeNetPart / PartNetE | 부위 분할 | ShapeNetPartDataset, PartNetEDataset | — | preprocessing/partnete/ |
| 내 데이터 | 임의 | DefaultDataset | 직접 작성 | 7.2절 참고 |
03최소한의 용어집
설정 파일과 로그를 읽는 데 꼭 필요한 것만 골랐습니다. 이 이상은 지금 몰라도 됩니다.
| 용어 | Python 개발자를 위한 설명 | 어디서 만나나 |
|---|---|---|
| epoch | 전체 학습 데이터를 처음부터 끝까지 1회 훑는 것. epoch=800이면 800번 반복. 가장 직접적인 학습 시간 조절 손잡이. | epoch = 800 |
| batch_size | 한 번에 GPU에 밀어넣는 장면(scene) 개수. 크면 빠르지만 GPU 메모리를 더 씀. 전체 GPU 합산 값입니다. | batch_size = 12 |
| learning rate (lr) | 모델이 한 걸음에 얼마나 크게 수정될지. 너무 크면 발산, 작으면 느림. 설정 파일 기본값을 함부로 바꾸지 마세요. | optimizer.lr |
| loss | “얼마나 틀렸는가” 점수. 학습 중 내려가야 정상. | 로그의 Loss 0.4213 |
| criteria | loss를 계산하는 함수 목록. 여러 개를 섞어 씁니다. | criteria=[dict(type="CrossEntropyLoss"...)] |
| optimizer / scheduler | 가중치를 갱신하는 알고리즘 / 학습률을 시간에 따라 바꾸는 규칙. | AdamW, OneCycleLR |
| backbone / head | 백본 = 특징 추출 본체, 헤드 = 특징을 최종 정답(클래스)으로 바꾸는 마지막 얇은 층. | model.backbone, seg_head |
| checkpoint (.pth) | 학습된 가중치 파일. model_best.pth(검증 최고 성능), model_last.pth(가장 최근). | exp/.../model/ |
| augmentation (증강) | 학습 시 데이터를 무작위로 회전·확대·색 변형해서 데이터를 늘리는 기법. data.train.transform 리스트가 그것. | RandomRotate, RandomScale |
| voxel / grid sampling | 점을 격자로 나눠 격자당 1점만 남기는 다운샘플링. grid_size=0.02는 2cm 격자. 메모리·속도에 가장 큰 영향을 주는 값. | GridSample(grid_size=0.02) |
| offset | Pointcept 고유 개념. 여러 장면을 하나로 이어 붙인 뒤 “어디서 끊기는지” 표시하는 누적합 배열. 장면 A가 100점, B가 250점이면 offset=[100, 350]. PyTorch Geometric의 batch와 같은 역할. | 모든 모델 입력 |
| mIoU | 주 평가 지표. 클래스별 (교집합/합집합) 비율의 평균. 0~1, 높을수록 좋음. 벽/바닥처럼 흔한 클래스에 점수가 쏠리지 않게 보정된 지표. | Val result: mIoU/... |
| mAcc / allAcc | 클래스별 정확도의 평균 / 전체 점 기준 정확도. allAcc는 흔한 클래스에 유리해서 실제 품질을 과대평가합니다. | 같은 로그 줄 |
| TTA | Test Time Augmentation. 테스트할 때 같은 장면을 회전·확대해 여러 번 예측하고 평균 냄. 정확도 ↑, 속도 ↓ (12배 이상 느려질 수 있음). | test_cfg.aug_transform |
| AMP | Mixed Precision. 16비트 연산으로 메모리를 줄이고 속도를 올림. enable_amp=True 켜두면 대체로 이득. | enable_amp = True |
| pre-training / fine-tuning | 라벨 없는 대량 데이터로 먼저 감을 잡게 한 뒤(pre-train), 내 소량 라벨 데이터로 마무리(fine-tune). 라벨링 비용을 줄이는 표준 전략. | weight=... 옵션 |
| DDP / world_size | 여러 GPU에 작업을 나누는 방식. world_size = 총 GPU 수. batch_size는 이 값으로 나누어떨어져야 합니다. | --num-gpus |
04설치
요구 사항
- Ubuntu 18.04 이상
- NVIDIA GPU + CUDA 11.3 이상 (또는 AMD GPU + ROCm 7.0 이상)
- PyTorch 1.10 이상 —
environment.yml기준은 PyTorch 2.5.0 + CUDA 12.4 - GPU 메모리: 최소 12GB, 권장 24GB 이상 (기본 설정 기준)
방법 A — conda (가장 무난)
# 로컬에 CUDA를 설치해 둔 경우 충돌 방지
unset CUDA_PATH
conda env create -f environment.yml --verbose
conda activate pointcept-torch2.5.0-cu12.4
environment.yml은 PyTorch, spconv, torch-scatter/cluster/sparse, flash-attention, 그리고 libs/pointops·libs/pointgroup_ops(이 저장소 안의 CUDA 확장)까지 한 번에 설치합니다. CUDA 커널을 소스에서 빌드하므로 20~60분 걸릴 수 있습니다.
방법 B — Docker (환경 문제를 피하고 싶다면)
docker run --gpus all -it --rm \
pointcept/pointcept:v1.6.0-pytorch2.5.0-cuda12.4-cudnn9-devel bash
사용 가능한 태그는 hub.docker.com/r/pointcept/pointcept/tags. 이 저장소에는 이미지를 직접 빌드하는 스크립트도 있습니다 (14장 참고).
설치 확인
export PYTHONPATH=./
python -c "import torch, pointcept; print(torch.__version__, torch.cuda.is_available())"
python -c "import spconv.pytorch; import pointops; print('ok')"
# 모델이 실제로 만들어지는지 확인 (가장 확실한 검증)
python - <<'EOF'
from pointcept.utils.config import Config
from pointcept.models import build_model
cfg = Config.fromfile("configs/scannet/semseg-pt-v3m1-0-base.py")
m = build_model(cfg.model)
print("params:", sum(p.numel() for p in m.parameters()) / 1e6, "M")
EOF
- flash-attn 빌드 실패 → 설치를 건너뛰고 설정에서
model.backbone.enable_flash=False로 두면 동작합니다(조금 느림). - spconv / CUDA 버전 불일치 →
spconv-cu124처럼 CUDA 버전 접미사가nvcc --version과 맞는지 확인. ModuleNotFoundError: pointcept→export PYTHONPATH=./를 잊은 것. 셸 스크립트를 쓰면 자동으로 처리됩니다.
05동작 원리: Config + Registry
Pointcept 전체를 관통하는 아이디어는 하나입니다. “객체를 코드로 생성하는 대신, dict로 서술한다.”
5.1 Registry: 문자열이 클래스가 된다
파이썬으로 이렇게 쓰는 대신
# 이렇게 안 씁니다
from pointcept.models.point_transformer_v3 import PointTransformerV3
backbone = PointTransformerV3(in_channels=6, enc_depths=(2,2,2,6,2), ...)
이렇게 씁니다.
# 이렇게 씁니다 — type 문자열이 클래스 이름표
backbone = dict(type="PT-v3m1", in_channels=6, enc_depths=(2,2,2,6,2))
model = build_model(backbone) # dict → 실제 객체
type을 뺀 나머지 키는 그대로 __init__의 키워드 인자로 전달됩니다. 즉 설정 파일의 dict를 읽는 것 = 그 클래스의 생성자 인자를 읽는 것입니다. 어떤 옵션이 있는지 모르겠으면 해당 클래스의 __init__ 시그니처를 열어보면 끝입니다.
레지스트리는 종류별로 나뉘어 있습니다.
| 레지스트리 | 설정 위치 | 정의된 곳 |
|---|---|---|
MODELS | model, model.backbone | pointcept/models/ |
DATASETS | data.train/val/test | pointcept/datasets/ |
TRANSFORMS | data.*.transform 리스트 | pointcept/datasets/transform.py |
LOSSES | model.criteria | pointcept/models/losses/ |
OPTIMIZERS / SCHEDULERS | optimizer, scheduler | pointcept/utils/optimizer.py, scheduler.py |
HOOKS | hooks 리스트 | pointcept/engines/hooks/ |
TRAINERS / TESTERS | train, test | pointcept/engines/train.py, test.py |
5.2 설정 파일 구조 해부
configs/scannet/semseg-pt-v3m1-0-base.py를 뼈대만 남기면 이렇습니다.
# ① 공통 기본값 상속 — configs/_base_/default_runtime.py의 값을 먼저 깔고 시작
_base_ = ["../_base_/default_runtime.py"]
# ② 실행 환경 — 여기를 가장 자주 만집니다
batch_size = 12 # 전체 GPU 합산
num_worker = 24 # 데이터 로딩 프로세스 수 (전체 합산)
enable_amp = True # 16비트 연산으로 메모리 절약
mix_prob = 0.8 # 두 장면을 섞는 증강(Mix3D) 확률
# ③ 모델 — "무엇을 학습시킬 것인가"
model = dict(
type="DefaultSegmentorV2", # 분할용 래퍼 (백본 + 분류 헤드)
num_classes=20, # 클래스 개수 ★ 내 데이터에 맞게 수정
backbone_out_channels=64,
backbone=dict(type="PT-v3m1", in_channels=6, ...), # in_channels ★ 입력 피처 차원
criteria=[
dict(type="CrossEntropyLoss", loss_weight=1.0, ignore_index=-1),
dict(type="LovaszLoss", mode="multiclass", loss_weight=1.0, ignore_index=-1),
],
)
# ④ 학습 스케줄
epoch = 800
optimizer = dict(type="AdamW", lr=0.006, weight_decay=0.05)
scheduler = dict(type="OneCycleLR", max_lr=[0.006, 0.0006], pct_start=0.05, ...)
param_dicts = [dict(keyword="block", lr=0.0006)] # 특정 층만 다른 학습률
# ⑤ 데이터
dataset_type = "ScanNetDataset"
data_root = "data/scannet" # ★ 데이터 위치
data = dict(
num_classes=20,
ignore_index=-1, # 이 라벨은 학습·평가에서 무시
names=["wall", "floor", ...], # ★ 클래스 이름 (로그 출력용, 개수가 맞아야 함)
train=dict(type=dataset_type, split="train", data_root=data_root,
transform=[ ...증강 파이프라인... ]),
val=dict(...), # 학습 중 매 epoch 간이 평가
test=dict(..., test_mode=True, test_cfg=dict(voxelize=..., aug_transform=[...])),
)
num_classes(2군데: model과 data), data.names, data_root, dataset_type, backbone.in_channels. 나머지는 처음엔 손대지 마세요.5.3 _base_ 상속과 오버라이드
_base_ = ["../_base_/default_runtime.py"]는 “저 파일의 변수들을 먼저 정의하고, 이 파일에서 다시 정의한 것만 덮어쓴다”는 뜻입니다. configs/_base_/default_runtime.py에 들어 있는 공통 기본값:
weight = None # 불러올 가중치 경로
resume = False # 중단된 학습 이어하기
evaluate = True # 매 epoch 검증 수행
test_only = False
save_path = "exp/default"
num_worker = 16
batch_size = 16
gradient_accumulation_steps = 1
epoch = 100
eval_epoch = 100 # 검증/체크포인트 횟수 (epoch % eval_epoch == 0 이어야 함)
clip_grad = None
sync_bn = False
enable_amp = False
empty_cache = False
find_unused_parameters = False
enable_wandb = True # ★ 기본 켜짐 — wandb 계정이 없으면 False로
wandb_project = "pointcept"
mix_prob = 0
param_dicts = None
hooks = [ # 학습 루프에 끼워 넣는 확장 지점
dict(type="CheckpointLoader"), # 가중치 로드
dict(type="ModelHook"),
dict(type="IterationTimer", warmup_iter=2),
dict(type="InformationWriter"), # 로그/텐서보드/wandb 기록
dict(type="SemSegEvaluator"), # mIoU 계산
dict(type="CheckpointSaver", save_freq=None),
dict(type="PreciseEvaluator", test_last=False), # 학습 끝나면 자동 테스트
]
train = dict(type="DefaultTrainer")
test = dict(type="SemSegTester", verbose=True)
5.4 파일을 고치지 않고 값만 바꾸기 — --options
실험할 때마다 설정 파일을 복사하지 않아도 됩니다. 커맨드라인에서 점(.)으로 중첩 키를 지정합니다.
python tools/train.py --config-file configs/scannet/semseg-pt-v3m1-0-base.py \
--options save_path=exp/scannet/test-run \
epoch=20 eval_epoch=20 \
batch_size=4 \
enable_wandb=False \
data.train.split=train \
model.backbone.enable_flash=False
리스트도 됩니다: KEY=1,2,3 또는 KEY=[a,b,c], 중첩은 KEY=[(1,2),(3,4)]. true/false는 자동으로 bool로, 숫자는 int/float로 변환됩니다.
06저장소 구조 지도
Pointcept/
├── configs/ ★ 설정 파일 292개. 여러분이 90% 시간을 보낼 곳
│ ├── _base_/default_runtime.py 공통 기본값
│ ├── scannet/ s3dis/ nuscenes/ ... 데이터셋별 설정
│ └── sonata/ concerto/ utonia/ 사전학습 프레임워크 설정
├── tools/ 진입점 스크립트
│ ├── train.py 학습
│ ├── test.py 테스트/평가
│ └── test_s3dis_6fold.py S3DIS 6-fold 집계
├── scripts/ ★ 실제로 호출할 셸 래퍼
│ ├── train.sh test.sh 학습/테스트 (권장 진입점)
│ └── build_image.sh build_wheels.sh 이 저장소 고유 빌드 도구 (14장)
├── pointcept/ 라이브러리 본체
│ ├── datasets/ 데이터셋 클래스 + transform.py(증강 44종) + preprocessing/
│ ├── models/ 백본·헤드·loss. 폴더 하나 = 모델 하나
│ ├── engines/ train.py(학습 루프) test.py(테스트 루프) hooks/(확장)
│ └── utils/ config, registry, optimizer, scheduler, visualization
├── libs/ CUDA 확장 소스 (설치 시 컴파일됨)
│ ├── pointops/ pointops2/ 이웃 탐색 등 포인트 연산
│ ├── pointgroup_ops/ 인스턴스 분할 전용
│ └── pointrope/ pointseg/
├── data/ ★ 데이터 위치 (심볼릭 링크 권장, git 미추적)
└── exp/ ★ 실행하면 자동 생성되는 실험 결과
실험 폴더(exp/) 구조 — 결과가 어디 있는지
exp/scannet/my-first-run/
├── config.py 실제로 사용된 설정 전체 사본 (재현용)
├── train.log 학습 로그 (사람이 읽는 기록)
├── test.log 테스트 로그
├── model/
│ ├── model_best.pth ★ 검증 mIoU 최고 시점의 가중치 — 배포에 쓸 파일
│ └── model_last.pth 가장 최근 (이어서 학습할 때 사용)
├── code/ 실행 시점의 코드 스냅샷 (train.sh가 자동 백업)
├── events.out.tfevents.* 텐서보드 로그
└── result/
├── {장면이름}_pred.npy ★ 점마다의 예측 클래스 (테스트 실행 후)
└── submit/ 벤치마크 제출용 파일 (해당 데이터셋일 때)
07데이터 준비
7.1 공개 데이터셋 사용하기
모든 데이터셋은 동일한 3단계를 따릅니다: 원본 다운로드 → 전처리 스크립트 실행 → data/에 링크.
예시 1 — S3DIS
# 1) 원본 다운로드 (Stanford3dDataset_v1.2.zip) 후 압축 해제
# 2) 전처리: 방(room)마다 폴더 하나 + .npy 여러 개로 변환
python pointcept/datasets/preprocessing/s3dis/preprocess_s3dis.py \
--splits Area_1 Area_2 Area_3 Area_4 Area_5 Area_6 \
--dataset_root /raw/Stanford3dDataset_v1.2 \
--output_root /processed/s3dis \
--parse_normal \
--num_workers 8
# 3) 코드베이스에 연결
mkdir -p data
ln -s /processed/s3dis ./data/s3dis
예시 2 — ScanNet v2
python pointcept/datasets/preprocessing/scannet/preprocess_scannet.py \
--dataset_root /raw/scannet \
--output_root /processed/scannet \
--num_workers 16
ln -s /processed/scannet ./data/scannet
data_root와 맞아야 합니다
설정 파일에 data_root = "data/scannet"으로 되어 있으므로, 저장소 루트에서 실행했을 때 ./data/scannet/train/, ./data/scannet/val/이 존재해야 합니다. 다른 곳에 두려면 --options data.train.data_root=/my/path data.val.data_root=/my/path data.test.data_root=/my/path로 지정하세요.7.2 내 데이터로 학습하기 가장 중요
새 파이썬 클래스를 작성할 필요가 없습니다. 내장 DefaultDataset이 “폴더 규약”만 지키면 알아서 읽습니다.
단계 1 — 폴더 규약에 맞춰 .npy 파일 생성
data/mydata/
├── train/
│ ├── scene_0001/ # 장면 하나 = 폴더 하나
│ │ ├── coord.npy # (N, 3) float32 — XYZ 좌표 [필수]
│ │ ├── color.npy # (N, 3) float32 — RGB 0~255 [선택]
│ │ ├── normal.npy # (N, 3) float32 — 법선 벡터 [선택]
│ │ ├── segment.npy # (N,) int32 — 클래스 라벨 [semseg에 필수]
│ │ └── instance.npy # (N,) int32 — 물체 ID [insseg에만 필요]
│ ├── scene_0002/
│ └── ...
├── val/
│ └── ...
└── test/
└── ...
인식되는 파일 이름은 coord, color, normal, strength(LiDAR 반사강도), segment, instance, pose 뿐입니다. 다른 이름의 .npy는 무시됩니다. segment.npy가 없으면 전부 -1(무시 라벨)로 채워집니다.
단계 2 — 변환 스크립트 예시 (PLY / LAS / CSV → npy)
# convert_mydata.py
import numpy as np, os, glob
import open3d as o3d # environment.yml에 포함되어 있음
LABEL_MAP = {"wall": 0, "floor": 1, "machine": 2, "pipe": 3} # 내 클래스 정의
OUT = "data/mydata"
def convert(ply_path, label_path, split, name):
pcd = o3d.io.read_point_cloud(ply_path)
coord = np.asarray(pcd.points, dtype=np.float32) # (N,3) 미터 단위 권장
color = (np.asarray(pcd.colors) * 255).astype(np.float32) # (N,3) 0~255로 맞춤
segment = np.load(label_path).astype(np.int32) # (N,) 0..C-1, 무시할 점은 -1
assert coord.shape[0] == color.shape[0] == segment.shape[0]
d = os.path.join(OUT, split, name)
os.makedirs(d, exist_ok=True)
np.save(os.path.join(d, "coord.npy"), coord)
np.save(os.path.join(d, "color.npy"), color)
np.save(os.path.join(d, "segment.npy"), segment)
# 법선이 필요하면 (in_channels에 normal을 포함할 경우)
# pcd.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
# np.save(os.path.join(d, "normal.npy"), np.asarray(pcd.normals, dtype=np.float32))
- 좌표 단위는 미터. 밀리미터 단위라면
grid_size=0.02(2cm)가 20mm가 아니라 2cm로 해석되어 다운샘플링이 전혀 안 됩니다.coord /= 1000.0으로 미터로 변환하세요. - 색상은 0~255 범위.
NormalizeColor변환이 255로 나누는 것을 전제합니다. 이미 0~1이면NormalizeColor를 빼세요. - 라벨은 0부터 연속. 클래스가 4개면 0,1,2,3이어야 합니다. 무시할 점만
-1(=ignore_index). - dtype 준수. coord/color/normal은 float32, segment/instance는 int32.
단계 3 — 내 설정 파일 만들기
기존 설정을 복사해서 ★ 표시된 부분만 고칩니다. 4클래스 + 색상만 있는 데이터 기준 예시입니다.
# configs/mydata/semseg-pt-v3m1-0-base.py
_base_ = ["../_base_/default_runtime.py"]
batch_size = 4 # GPU 1장, 12GB 기준
num_worker = 8
mix_prob = 0.8
enable_amp = True
enable_wandb = False # wandb 계정 없으면 반드시 끄기
num_classes = 4 ★ 내 클래스 수
class_names = ["wall", "floor", "machine", "pipe"] ★
model = dict(
type="DefaultSegmentorV2",
num_classes=num_classes, ★
backbone_out_channels=64,
backbone=dict(
type="PT-v3m1",
in_channels=3, ★ color(3)만 쓸 것이므로 3. color+normal이면 6
order=("z", "z-trans", "hilbert", "hilbert-trans"),
stride=(2, 2, 2, 2),
enc_depths=(2, 2, 2, 6, 2),
enc_channels=(32, 64, 128, 256, 512),
enc_num_head=(2, 4, 8, 16, 32),
enc_patch_size=(1024, 1024, 1024, 1024, 1024),
dec_depths=(2, 2, 2, 2),
dec_channels=(64, 64, 128, 256),
dec_num_head=(4, 4, 8, 16),
dec_patch_size=(1024, 1024, 1024, 1024),
mlp_ratio=4, qkv_bias=True, drop_path=0.3,
shuffle_orders=True, pre_norm=True,
enable_rpe=False, enable_flash=True, # flash-attn 미설치면 False
enc_mode=False,
),
criteria=[
dict(type="CrossEntropyLoss", loss_weight=1.0, ignore_index=-1),
dict(type="LovaszLoss", mode="multiclass", loss_weight=1.0, ignore_index=-1),
],
)
epoch = 100 ★ 데이터가 적으면 100~300으로 시작
eval_epoch = 100 epoch % eval_epoch == 0 이어야 함
optimizer = dict(type="AdamW", lr=0.006, weight_decay=0.05)
scheduler = dict(type="OneCycleLR", max_lr=[0.006, 0.0006],
pct_start=0.05, anneal_strategy="cos",
div_factor=10.0, final_div_factor=1000.0)
param_dicts = [dict(keyword="block", lr=0.0006)]
dataset_type = "DefaultDataset" ★ 내장 범용 데이터셋
data_root = "data/mydata" ★
data = dict(
num_classes=num_classes, ★
ignore_index=-1,
names=class_names, ★
train=dict(
type=dataset_type, split="train", data_root=data_root,
transform=[
dict(type="CenterShift", apply_z=True),
dict(type="RandomDropout", dropout_ratio=0.2, dropout_application_ratio=0.2),
dict(type="RandomRotate", angle=[-1, 1], axis="z", center=[0, 0, 0], p=0.5),
dict(type="RandomRotate", angle=[-1/64, 1/64], axis="x", p=0.5),
dict(type="RandomRotate", angle=[-1/64, 1/64], axis="y", p=0.5),
dict(type="RandomScale", scale=[0.9, 1.1]),
dict(type="RandomFlip", p=0.5),
dict(type="RandomJitter", sigma=0.005, clip=0.02),
dict(type="ChromaticAutoContrast", p=0.2, blend_factor=None),
dict(type="ChromaticTranslation", p=0.95, ratio=0.05),
dict(type="ChromaticJitter", p=0.95, std=0.05),
dict(type="GridSample", grid_size=0.02, hash_type="fnv", mode="train",
return_grid_coord=True),
dict(type="SphereCrop", point_max=102400, mode="random"),
dict(type="CenterShift", apply_z=False),
dict(type="NormalizeColor"),
dict(type="ToTensor"),
dict(type="Collect", keys=("coord", "grid_coord", "segment"),
feat_keys=("color",)), ★ in_channels=3과 일치해야 함
],
test_mode=False,
),
val=dict(
type=dataset_type, split="val", data_root=data_root,
transform=[
dict(type="CenterShift", apply_z=True),
dict(type="Copy", keys_dict={"segment": "origin_segment"}),
dict(type="GridSample", grid_size=0.02, hash_type="fnv", mode="train",
return_grid_coord=True, return_inverse=True),
dict(type="CenterShift", apply_z=False),
dict(type="NormalizeColor"),
dict(type="ToTensor"),
dict(type="Collect",
keys=("coord", "grid_coord", "segment", "origin_segment", "inverse"),
feat_keys=("color",)),
],
test_mode=False,
),
test=dict(
type=dataset_type, split="test", data_root=data_root,
transform=[
dict(type="CenterShift", apply_z=True),
dict(type="NormalizeColor"),
],
test_mode=True,
test_cfg=dict(
voxelize=dict(type="GridSample", grid_size=0.02, hash_type="fnv",
mode="test", return_grid_coord=True),
crop=None,
post_transform=[
dict(type="CenterShift", apply_z=False),
dict(type="ToTensor"),
dict(type="Collect", keys=("coord", "grid_coord", "index"),
feat_keys=("color",)),
],
# TTA 없음(빠름). 정확도를 더 원하면 회전/스케일 조합을 추가
aug_transform=[[dict(type="RandomRotateTargetAngle", angle=[0],
axis="z", center=[0, 0, 0], p=1)]],
),
),
)
Collect와 in_channels의 관계
Collect(feat_keys=("color", "normal"))은 color(3열)와 normal(3열)을 옆으로 이어붙여 6열짜리 feat를 만듭니다. 이 숫자가 곧 backbone.in_channels입니다. feat_keys=("coord","color")면 6, ("color",)면 3. 불일치하면 학습 시작 직후 shape 에러가 납니다.단계 4 — 실행
sh scripts/train.sh -g 1 -d mydata -c semseg-pt-v3m1-0-base -n exp01
-d mydata는 configs/mydata/ 폴더를, -c는 그 안의 파일명(확장자 제외)을 가리킵니다.
08학습 실행
8.1 기본 명령
export CUDA_VISIBLE_DEVICES=0,1 # 쓸 GPU 지정 (선택)
# 권장: 셸 스크립트 (PYTHONPATH 설정 + 코드 백업 + exp 폴더 생성을 대신 해줌)
sh scripts/train.sh -g 2 -d scannet -c semseg-pt-v3m1-0-base -n my-run
# 직접 호출 (디버깅·IDE 연동에 편리)
export PYTHONPATH=./
python tools/train.py \
--config-file configs/scannet/semseg-pt-v3m1-0-base.py \
--num-gpus 2 \
--options save_path=exp/scannet/my-run
8.2 scripts/train.sh 옵션 전체
| 옵션 | 의미 | 기본값 | 예 |
|---|---|---|---|
-d | 데이터셋 폴더명 = configs/<여기>/ | scannet | -d s3dis |
-c | 설정 파일명 (확장자 제외) | — | -c semseg-pt-v3m1-0-base |
-n | 실험 이름 = exp/<-d>/<-n>/ | debug | -n ptv3-lr006 |
-g | GPU 개수 | 자동 감지 | -g 4 |
-p | 파이썬 인터프리터 경로 | python | -p /opt/conda/envs/pc/bin/python |
-w | 초기 가중치 경로 (fine-tuning) | None | -w /path/model_best.pth |
-r | 중단 지점부터 재개 | false | -r true |
-m | 머신(노드) 수 — 다중 서버 | 1 | -m 2 |
-r true의 동작
재개 시 스크립트는 새 설정 파일이 아니라 exp/<d>/<n>/config.py(첫 실행 때 저장된 사본)과 model/model_last.pth를 사용합니다. 즉 재개는 항상 원래 실험과 완전히 동일한 조건으로 이어집니다.8.3 로그 읽는 법
[2026-08-26 14:02:11 pointcept] INFO Train: [12/100][ 340/1201] Data 0.012 (0.020)
Batch 0.284 (0.291) Remain 06:12:44 loss: 0.4213 lr: 0.005210
[2026-08-26 14:35:02 pointcept] INFO Val result: mIoU/macroF1/mAcc/mPrecision/allAcc
0.7412/0.8203/0.8155/0.8290/0.9021.
[2026-08-26 14:35:02 pointcept] INFO Class_0-wall Metrics: IoU=0.8512, accuracy=0.9102, ...
[2026-08-26 14:35:03 pointcept] INFO Best validation mIoU updated to: 0.7412
[2026-08-26 14:35:03 pointcept] INFO Saving checkpoint to: exp/.../model/model_last.pth
| 표시 | 의미 | 이럴 때 문제 |
|---|---|---|
Train: [12/100][340/1201] | 12번째 epoch(총 100), 그 안의 340번째 배치(총 1201) | — |
Data 0.012 (0.020) | 데이터 로딩 시간, (괄호는 평균) | Batch 시간에 근접하면 데이터 로딩 병목 → num_worker 증가 또는 cache=True |
Batch 0.284 | 배치 1개 전체 처리 시간(초) | — |
Remain 06:12:44 | 예상 잔여 시간 | 학습 규모 감 잡기용 |
loss: 0.4213 | 현재 손실 | 내려가지 않으면 lr 과다 / 라벨 오류 / 데이터 스케일 문제 |
mIoU 0.7412 | ★ 검증 성능. 이 숫자로 모델 품질을 판단 | 0에 가깝게 정체 → 데이터·클래스 매핑 점검 |
Class_0-wall IoU=0.85 | 클래스별 성능 | 특정 클래스만 0 → 그 클래스 샘플 부족 또는 라벨 오류 |
8.4 실시간 모니터링
# 텐서보드 (별도 설정 불필요, 항상 켜짐)
tensorboard --logdir exp/scannet/my-run
# 로그 실시간 확인
tail -f exp/scannet/my-run/train.log
# wandb는 기본으로 켜져 있음 → 계정이 없으면 반드시 끄기
# ① 설정 파일에 enable_wandb = False, 또는
# ② --options enable_wandb=False, 또는
# ③ 터미널에서 `wandb login` 후 사용
8.5 GPU 개수와 batch_size의 관계
Pointcept의 batch_size와 num_worker는 전체 GPU 합산 값입니다. 내부에서 GPU 수로 나눕니다.
batch_size_per_gpu = batch_size // num_gpus # 나누어떨어져야 함 (assert)
num_worker_per_gpu = num_worker // num_gpus
따라서 GPU 1장에서 batch_size=12 설정을 그대로 쓰면 12개 장면이 한 GPU에 올라가 대부분 OOM입니다. GPU를 줄이면 batch_size도 함께 줄이세요.
| 환경 | 권장 시작 설정 |
|---|---|
| GPU 1장 / 12GB | batch_size=2 num_worker=4 enable_amp=True + grid_size=0.05 |
| GPU 1장 / 24GB | batch_size=4 num_worker=8 enable_amp=True |
| GPU 4장 / 24GB | batch_size=12 num_worker=24 (설정 파일 기본값) |
| GPU 8장 / 40GB+ | 설정 파일 기본값 그대로 |
8.6 학습을 짧게 돌려서 파이프라인만 검증하기
본격 학습 전에 “데이터가 제대로 읽히고 모델이 도는지”만 5분 안에 확인하는 방법입니다.
export PYTHONPATH=./
python tools/train.py --config-file configs/mydata/semseg-pt-v3m1-0-base.py \
--num-gpus 1 \
--options save_path=exp/mydata/smoke \
epoch=2 eval_epoch=2 \
batch_size=1 num_worker=2 \
enable_wandb=False \
hooks="[dict(type='CheckpointLoader'),dict(type='IterationTimer'),dict(type='InformationWriter'),dict(type='SemSegEvaluator'),dict(type='CheckpointSaver')]"
hooks에서 PreciseEvaluator를 빼면 학습 직후 자동 실행되는 전체 테스트를 건너뛰어 훨씬 빨리 끝납니다.
epoch % eval_epoch == 0 — 위반하면 시작하자마자 AssertionError. 내부적으로 data.train.loop = epoch // eval_epoch로 “한 epoch 안에서 데이터셋을 몇 바퀴 도는가”가 결정되기 때문입니다. epoch=800, eval_epoch=100이면 100번 검증하고 각 검증 구간마다 데이터를 8바퀴 돕니다.09평가 · 테스트 실행
9.1 학습 중 검증(val) vs 최종 테스트(test)의 차이
학습 중 검증 (SemSegEvaluator) | 최종 테스트 (SemSegTester) | |
|---|---|---|
| 대상 | 다운샘플링(voxelize)된 점 | 원본 밀도의 모든 점 |
| 방식 | 1회 예측 | 장면을 여러 조각으로 나눠 전부 커버 + TTA로 여러 번 예측 후 평균 |
| 속도 | 빠름 (매 epoch) | 느림 (TTA 13종이면 13배) |
| 점수 | 참고용, 약간 낮게 나옴 | 보고할 수 있는 정확한 값 |
| 산출물 | 로그의 mIoU | 로그 + result/*_pred.npy + 제출 파일 |
PreciseEvaluator 훅이 기본 hooks에 들어 있어서 학습이 끝나면 테스트가 자동으로 한 번 실행됩니다. 따로 돌릴 필요가 없는 경우도 많습니다.
9.2 테스트 명령
# 학습이 만든 실험 폴더 기준 (config.py를 자동으로 재사용)
sh scripts/test.sh -g 1 -d scannet -n my-run -w model_best
# 직접 호출
export PYTHONPATH=./
python tools/test.py \
--config-file exp/scannet/my-run/config.py \
--num-gpus 1 \
--options save_path=exp/scannet/my-run \
weight=exp/scannet/my-run/model/model_best.pth
-w는 exp/<d>/<n>/model/<-w>.pth로 해석됩니다. 기본값 model_best. 마지막 시점 가중치를 쓰려면 -w model_last.
9.3 결과 파일 사용하기
import numpy as np
pred = np.load("exp/scannet/my-run/result/scene0011_00_pred.npy")
# (N,) int — 원본 점 순서 그대로, 각 점의 클래스 인덱스
print(pred.shape, np.unique(pred, return_counts=True))
이 배열은 원본 coord.npy와 인덱스가 1:1 대응합니다. 그대로 색을 입혀 시각화하거나 후처리 파이프라인에 넘기면 됩니다.
# 시각화 예시 (open3d)
import numpy as np, open3d as o3d
coord = np.load("data/mydata/test/scene_0001/coord.npy")
pred = np.load("exp/mydata/exp01/result/scene_0001_pred.npy")
palette = np.random.RandomState(0).randint(0, 255, (pred.max() + 1, 3)) / 255.0
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(coord)
pcd.colors = o3d.utility.Vector3dVector(palette[pred])
o3d.io.write_point_cloud("pred.ply", pcd) # CloudCompare 등으로 열어보기
pointcept/utils/visualization.py에도 ply 저장 헬퍼가 있습니다.
9.4 테스트를 빠르게 — TTA 끄기
기본 설정의 aug_transform에는 회전 4종 × 스케일 3종 + flip = 13가지가 들어 있어 13배 느립니다. 개발 중에는 하나만 남기세요.
python tools/test.py --config-file exp/mydata/exp01/config.py --num-gpus 1 \
--options weight=exp/mydata/exp01/model/model_best.pth \
save_path=exp/mydata/exp01 \
data.test.test_cfg.aug_transform="[[dict(type='RandomRotateTargetAngle',angle=[0],axis='z',center=[0,0,0],p=1)]]"
설정 파일에서 직접 고치는 편이 더 읽기 쉽습니다. 최종 성능 보고 시에만 TTA를 다시 켜세요.
9.5 학습 없이 평가만 하기
python tools/train.py --config-file configs/mydata/semseg-pt-v3m1-0-base.py \
--num-gpus 1 --options test_only=True weight=/path/to/model_best.pth \
save_path=exp/mydata/eval-only
10추론: 내 데이터에 예측 붙이기 서비스 연동
tools/test.py는 “데이터셋 폴더 전체”를 대상으로 합니다. 서비스 코드에서 넘파이 배열 하나를 넣고 예측 배열 하나를 받는 함수가 필요하다면 아래처럼 직접 호출하면 됩니다.
10.1 최소 추론 스크립트 (복사해서 바로 쓸 수 있음)
# infer.py — 사용법: python infer.py
import numpy as np
import torch
from collections import OrderedDict
from pointcept.utils.config import Config
from pointcept.models import build_model
from pointcept.datasets.transform import Compose
from pointcept.datasets.utils import collate_fn
def load_model(config_path, weight_path, device="cuda"):
cfg = Config.fromfile(config_path) # exp/.../config.py 를 쓰는 것이 가장 안전
model = build_model(cfg.model).to(device).eval()
ckpt = torch.load(weight_path, map_location="cpu", weights_only=False)
# DDP로 저장된 가중치는 키가 "module." 로 시작 → 제거
state = OrderedDict(
(k[7:] if k.startswith("module.") else k, v)
for k, v in ckpt["state_dict"].items()
)
model.load_state_dict(state, strict=True)
print(f"loaded epoch={ckpt.get('epoch')}")
return cfg, model
def build_infer_transform(grid_size=0.02, feat_keys=("color",)):
"""학습 때의 val 파이프라인과 동일하게 구성 (증강은 제외).
return_inverse=True 가 핵심: 다운샘플된 예측을 원본 점 개수로 되돌리는 매핑."""
return Compose([
dict(type="CenterShift", apply_z=True),
dict(type="GridSample", grid_size=grid_size, hash_type="fnv", mode="train",
return_grid_coord=True, return_inverse=True),
dict(type="CenterShift", apply_z=False),
dict(type="NormalizeColor"),
dict(type="ToTensor"),
dict(type="Collect", keys=("coord", "grid_coord", "inverse"),
feat_keys=feat_keys),
])
@torch.no_grad()
def predict(model, transform, coord, color=None, normal=None, device="cuda"):
"""coord: (N,3) float32 미터 / color: (N,3) float32 0~255
반환: (N,) int64 — 원본 점 순서 그대로의 클래스 인덱스"""
data = {"coord": coord.astype(np.float32)}
if color is not None:
data["color"] = color.astype(np.float32)
if normal is not None:
data["normal"] = normal.astype(np.float32)
data = transform(data)
inverse = data.pop("inverse") # (N,) 원본 → 복셀 매핑
batch = collate_fn([data]) # offset 등 배치 정보 생성
batch = {k: (v.to(device) if isinstance(v, torch.Tensor) else v)
for k, v in batch.items()}
seg_logits = model(batch)["seg_logits"] # (M, num_classes) — M은 복셀 개수
voxel_pred = seg_logits.argmax(dim=1).cpu() # (M,)
return voxel_pred[inverse].numpy() # (N,) 원본 해상도로 복원
if __name__ == "__main__":
cfg, model = load_model(
"exp/mydata/exp01/config.py",
"exp/mydata/exp01/model/model_best.pth",
)
transform = build_infer_transform(grid_size=0.02, feat_keys=("color",))
coord = np.load("sample/coord.npy")
color = np.load("sample/color.npy")
pred = predict(model, transform, coord, color)
print(pred.shape, np.bincount(pred, minlength=cfg.data.num_classes))
np.save("sample_pred.npy", pred)
feat_keys— 학습 설정의Collect(feat_keys=...)와 완전히 동일한 순서·구성이어야 합니다. color만으로 학습했으면 추론도 color만.grid_size— 학습 때 값과 같아야 합니다. 다르면 성능이 크게 떨어집니다.- 입력 스케일 — 좌표는 미터, 색은 0~255.
- 설정 파일은
exp/.../config.py를 쓰세요. 원본configs/파일은 그 사이에 수정되었을 수 있습니다.
10.2 점마다의 특징 벡터(임베딩) 뽑기
분류 결과 대신 고차원 특징이 필요하면 (유사도 검색, 클러스터링, 다른 모델의 입력 등) return_point=True를 씁니다.
out = model(batch, return_point=True)
point = out["point"]
feat = point.feat # (M, C) — 점(복셀)마다의 특징 벡터
xyz = point.coord # (M, 3)
print(feat.shape)
DefaultSegmentorV2, DINOEnhancedSegmentor가 이 인자를 지원합니다. 사전학습 모델(Sonata / Concerto / Utonia)은 이 용도로 만들어진 것이라, 라벨 없이도 의미 있는 특징을 냅니다.
10.3 배치로 여러 장면 한꺼번에
datas = [transform({"coord": c, "color": col}) for c, col in scenes]
inverses = [d.pop("inverse") for d in datas]
batch = collate_fn(datas) # offset이 [n1, n1+n2, ...]로 자동 생성됨
# 예측 후 offset 경계로 잘라서 각 장면에 되돌림
offset = batch["offset"].tolist()
bounds = list(zip([0] + offset[:-1], offset))
10.4 CPU에서 추론할 수 있나?
권장하지 않습니다. spconv·flash-attention·pointops 등 CUDA 전용 커널에 의존하는 백본이 많아 대부분 CPU에서 실행되지 않습니다. 배포 환경에도 NVIDIA GPU를 준비하세요.
11사전학습 모델 활용 (라벨이 적을 때)
라벨링 비용이 문제라면 이 장이 가장 실용적입니다. “이미 학습된 모델을 가져와 내 소량 데이터로 마무리한다”는 전략입니다.
11.1 공개 사전학습 가중치
| 이름 | 성격 | 배포처 |
|---|---|---|
| Sonata | 자기지도 사전학습 PTv3. 라벨 없이 학습된 범용 3D 특징 추출기 | huggingface.co/facebook/sonata · 추론 데모 저장소 |
| Concerto | 2D-3D 결합 자기지도. 공간 표현 학습 | huggingface.co/Pointcept/Concerto |
| Utonia | “모든 포인트 클라우드를 위한 하나의 인코더” — 최신 | huggingface.co/Pointcept/Utonia · 추론 저장소 |
| PPT | 여러 데이터셋 공동학습 가중치 | Pointcept README 모델 주 |
11.2 가중치를 불러와 fine-tuning
# 방법 A: 셸 스크립트의 -w 옵션
sh scripts/train.sh -g 1 -d mydata -c semseg-pt-v3m1-0-base -n ft01 \
-w /path/to/pretrained.pth
# 방법 B: --options
python tools/train.py --config-file configs/mydata/semseg-pt-v3m1-0-base.py \
--num-gpus 1 --options save_path=exp/mydata/ft01 weight=/path/to/pretrained.pth
CheckpointLoader 훅이 strict=False로 로드하므로 클래스 수가 달라 분류 헤드 크기가 안 맞아도 그 부분만 건너뛰고 백본은 정상적으로 로드됩니다. 로그에서 Missing keys / Unexpected keys를 확인하세요 — 백본 키가 대량으로 missing이면 잘못 로드된 것입니다.
키 이름 규칙이 다른 가중치는 keywords/replacement로 맞춥니다.
hooks = [
dict(type="CheckpointLoader", keywords="module.backbone.", replacement="module."),
...
]
11.3 fine-tuning 시 권장 설정
epoch = 100 # 처음부터 학습보다 훨씬 짧게
eval_epoch = 100
optimizer = dict(type="AdamW", lr=0.001, weight_decay=0.05) # 기본의 1/5 ~ 1/10
param_dicts = [dict(keyword="block", lr=0.0001)] # 백본은 더 작은 lr
model = dict(
...,
freeze_backbone=False, # True로 하면 백본을 얼리고 헤드만 학습 (데이터 아주 적을 때)
)
참고 설정: configs/scannet/semseg-spunet-v1m1-4-ft.py. 데이터가 극히 적다면 freeze_backbone=True로 시작해 헤드만 학습하고, 이후 전체를 낮은 lr로 푸는 2단계 전략이 안전합니다.
11.4 라벨 없이 내 데이터로 사전학습하기
라벨은 없지만 스캔 데이터는 많다면, segment.npy 없이 coord/color만으로 사전학습을 돌릴 수 있습니다.
sh scripts/train.sh -g 4 -d scannet -c pretrain-msc-v1m1-0-spunet-base -n msc-pretrain
# 이후 그 가중치로 소량 라벨 데이터를 fine-tuning
설정을 내 데이터로 옮길 때는 data_root, dataset_type="DefaultDataset", transform의 feat_keys만 맞추면 됩니다. Sonata/Concerto/Utonia 설정(configs/sonata/ 등)은 GPU 8장 이상을 전제로 하니 소규모라면 MSC부터 시작하세요.
12커스터마이징 레시피
“이걸 하고 싶은데 어디를 고치지?”에 대한 즉답 모음입니다.
| 하고 싶은 것 | 고칠 곳 |
|---|---|
| 클래스 개수 변경 | model.num_classes, data.num_classes, data.names — 3곳 모두 |
| 학습 시간 단축 | epoch를 줄이고 eval_epoch를 같은 값 또는 약수로 |
| GPU 메모리 부족 | ① batch_size ↓ ② enable_amp=True ③ GridSample.grid_size ↑(0.02→0.05) ④ SphereCrop.point_max ↓ ⑤ empty_cache=True ⑥ 백본을 SpUNet-v1m1로 |
| 학습이 너무 느림 | num_worker ↑, enable_amp=True, 로그의 Data 시간이 크면 data.train.cache=True |
| 입력 피처 바꾸기 (좌표만 / 색만 / 강도 포함) | Collect(feat_keys=...) + backbone.in_channels를 동시에 |
| 증강 끄기/추가 | data.train.transform 리스트에서 항목 추가·삭제 (부록 15.2의 이름 목록 참고) |
| 회전 증강 범위 조절 | RandomRotate(angle=[-1, 1]) — 단위는 π 배수. [-1,1]이면 ±180° |
| 손실 함수 변경 | model.criteria 리스트. 클래스 불균형이 심하면 FocalLoss나 LovaszLoss 가중치 ↑ |
| 특정 클래스를 학습에서 제외 | 해당 점의 segment를 -1로 (= ignore_index) |
| 옵티마이저/스케줄러 변경 | optimizer = dict(type="SGD"|"Adam"|"AdamW"|"Muon_KIMI", ...), scheduler = dict(type="OneCycleLR"|"CosineAnnealingLR"|"PolyLR"|"MultiStepLR"|"ExpLR"|"MultiStepWithWarmupLR", ...) |
| 백본만 교체 | model.backbone.type과 그 하위 인자를 통째로. 다른 설정 파일의 backbone 블록을 그대로 복사하는 것이 가장 안전 |
| gradient 폭주 방지 | clip_grad = 1.0 |
| 큰 batch 효과를 메모리 없이 | gradient_accumulation_steps = 4 (batch_size 4 → 실효 16) |
| 매 epoch 체크포인트 저장 | hooks의 CheckpointSaver(save_freq=1) |
| 클래스별 상세 지표 기록 | SemSegEvaluator(write_cls_metrics=True) |
| wandb 끄기 | enable_wandb = False |
| 재현성 확보 | seed = 42 (미지정 시 무작위 생성 후 config.py에 기록됨) |
| 다중 서버 학습 | -m 2 + 각 노드에서 --machine-rank 다르게, --dist-url tcp://MASTER:PORT |
12.1 GPU 메모리 부족(OOM) 대응 순서
위에서부터 차례로 시도하세요. 위쪽이 성능 손실이 적습니다.
1. enable_amp = True # 손실 거의 없음, 메모리 30~40% 절약
2. batch_size = 2 # 가장 직접적
3. gradient_accumulation_steps = 4 # batch_size를 줄인 만큼 보상
4. SphereCrop(point_max=102400 → 51200) # 한 장면에서 쓸 최대 점 수
5. GridSample(grid_size=0.02 → 0.04) # 해상도 절반, 성능 다소 하락
6. empty_cache = True / empty_cache_per_epoch = True # 느려지지만 파편화 완화
7. backbone: PT-v3m1 → SpUNet-v1m1 # 훨씬 가벼운 모델
12.2 데이터 로딩 병목 대응
num_worker = 16 # CPU 코어 수에 맞게 (전체 합산)
data = dict(train=dict(cache=True)) # 공유메모리 캐시 — 데이터가 RAM에 들어갈 때만
# 캐시를 미리 채우는 훅
hooks = [dict(type="DataCacheOperator", data_root="data/mydata", split="train"), ...]
num_worker를 크게 잡으면 Too many open files가 납니다. scripts/train.sh는 ulimit -n 65536을 자동 설정하지만, tools/train.py를 직접 실행할 때는 직접 ulimit -n 65536을 해주세요.13트러블슈팅
| 증상 / 에러 메시지 | 원인 | 해결 |
|---|---|---|
ModuleNotFoundError: No module named 'pointcept' | PYTHONPATH 미설정 | export PYTHONPATH=./ (저장소 루트에서). 또는 scripts/train.sh 사용 |
AssertionError (default_setup 근처) | epoch % eval_epoch != 0 또는 batch_size % num_gpus != 0 | 두 조건을 모두 만족하도록 값 조정 |
CUDA out of memory | 배치/해상도 과다 | 12.1절 순서대로 |
| shape mismatch (첫 배치에서 즉시) | Collect.feat_keys의 총 열 수 ≠ backbone.in_channels | 둘을 일치시킴. color=3, normal=3, coord=3, strength=1 |
IndexError / CUDA assert: t >= 0 && t < n_classes | 라벨 값이 num_classes 범위를 벗어남 | np.unique(segment)가 -1 또는 0..C-1인지 확인 |
| 학습은 도는데 mIoU가 0 근처에서 안 오름 | ① 좌표 단위 오류(mm) ② 라벨 매핑 오류 ③ 색 범위 오류 ④ lr 과다 | 7.2절 체크리스트 재확인. 소량 데이터로 과적합이 되는지 먼저 테스트 |
| wandb 로그인 프롬프트에서 멈춤 | enable_wandb가 기본 True | --options enable_wandb=False 또는 wandb login |
Too many open files | num_worker 과다 + ulimit 낮음 | ulimit -n 65536 |
ImportError: flash_attn | flash-attention 미설치 | --options model.backbone.enable_flash=False |
ImportError: MinkowskiEngine / Swin3D | 해당 백본 선택 + 미설치 | 다른 백본 사용, 또는 pointcept/models/__init__.py 주석 해제 후 설치 |
| 다중 GPU에서 hang(멈춤) | 일부 파라미터가 forward에 미사용 | find_unused_parameters=True |
포트 충돌 Address already in use | 이전 실행의 좀비 프로세스 | pkill -f tools/train.py 후 재실행, 또는 --dist-url tcp://127.0.0.1:29511 |
| 테스트가 끝나지 않음 | TTA 13종 × 큰 장면 | 9.4절대로 aug_transform 축소 |
-r true로 재개했는데 설정 변경이 반영 안 됨 | 재개는 exp/.../config.py를 사용 | 의도한 동작. 새 설정을 쓰려면 새 실험 이름으로 시작 |
결과 *_pred.npy가 안 생김 | 이미 존재하면 건너뜀 | result/ 폴더를 지우고 다시 테스트 |
13.1 “일단 학습이 되긴 하는가”를 확인하는 최소 실험
모델·데이터 파이프라인이 정상인지 보는 가장 확실한 방법은 장면 1~2개로 일부러 과적합시켜 보는 것입니다. 정상이라면 mIoU가 0.9 이상으로 올라갑니다. 안 오르면 데이터나 설정에 문제가 있는 것이고, 이 단계에서 잡는 편이 훨씬 빠릅니다.
# train/val 모두 같은 소수 장면을 가리키게 하고 짧게 돌림
python tools/train.py --config-file configs/mydata/semseg-pt-v3m1-0-base.py \
--num-gpus 1 --options save_path=exp/mydata/overfit \
epoch=50 eval_epoch=50 batch_size=1 num_worker=2 enable_wandb=False \
data.train.transform="[]" # ← 증강 제거는 별도로 설정 파일에서 처리하는 편이 안전
참고: transform 전체를 --options로 덮어쓰는 것은 따옴표 이스케이프가 까다롭습니다. 실험용 설정 파일을 하나 복사해서 쓰는 편이 실수가 적습니다.
14이 저장소의 빌드 도구
이 저장소(fork)에는 업스트림에 없는 이미지/휠 빌드 도구가 추가되어 있습니다. 환경 구성을 재현 가능하게 만들고 싶을 때 사용합니다.
| 스크립트 | 용도 | 사용 예 |
|---|---|---|
scripts/build_image.sh | PyTorch/CUDA 버전을 지정해 Docker 이미지를 빌드 | sh scripts/build_image.sh -t 2.5.0 -c 12.4 --cudnn 9 |
scripts/build_wheels.sh | CUDA 확장(pointops 등)을 미리 컴파일한 wheel 생성 | 빌드 시간이 긴 확장을 여러 머신에 배포할 때 |
scripts/verify_wheels.sh | 생성된 wheel이 정상 import되는지 검증 | 배포 전 확인 |
wheelhouse/linux-amd64, linux-arm64 | 빌드된 wheel 보관소 | pip install wheelhouse/linux-amd64/*.whl |
scripts/create_tars.sh | 배포용 아카이브 생성 | — |
빌드 시 TORCH_CUDA_ARCH_LIST="8.0 8.6 8.9 9.0"가 지정됩니다 — A100/RTX30·40/H100 대상입니다. 다른 GPU(예: RTX 20xx = 7.5)라면 이 값을 조정해야 합니다.
15부록: 등록 이름 전체 목록
설정 파일의 type="..."에 쓸 수 있는 문자열입니다. 이름을 알면 해당 클래스의 __init__을 열어 인자를 확인할 수 있습니다.
15.1 모델
래퍼(작업별): DefaultSegmentor, DefaultSegmentorV2, DefaultLORASegmentorV2, DINOEnhancedSegmentor, DefaultClassifier
백본: PT-v3m1, PT-v3m2, PT-v3m3, PT-v2m1, PT-v2m2, PT-v2m3, PointTransformer-Seg26/38/50, PointTransformer-Cls26/38/50, PointTransformer-PartSeg26/38/50, SpUNet-v1m1, SpUNet-v1m2, SpUNet-v1m3, SpUNetNoSkipBase, MinkUNet14/18/34/50/101(+A/B/C/D 변형), OACNNs, SPVCNN, OctFormer-v1m1, ST-v1m1, ST-v1m2, Swin3D-v1m1, LitePT-v1
인스턴스 분할: PG-v1m1, PG-v1m2, SGIFormer-v1m1
분할 보조: CAC-v1m1
사전학습 프레임워크: MSC-v1m1, MSC-v1m2, PPT-v1m1, PPT-v1m2, PPT-v1m3, Sonata-v1m1, Sonata-v1m2, Sonata-v1m3, Concerto-v1m1, Concerto-v1m2_distill, Utonia-v1m1
15.2 데이터 변환 (transform) 44종
| 분류 | 이름 |
|---|---|
| 구조/필수 | Collect, Copy, Update, ToTensor, ShufflePoint |
| 좌표 정규화 | NormalizeCoord, PositiveShift, CenterShift, PointClip |
| 기하 증강 | RandomShift, RandomRotate, RandomRotateTargetAngle, RandomScale, RandomFlip, RandomJitter, ClipGaussianJitter, ElasticDistortion, RandomDropout |
| 색상 증강 | NormalizeColor, ChromaticAutoContrast, ChromaticTranslation, ChromaticJitter, RandomColorGrayScale, RandomColorJitter, HueSaturationTranslation, RandomDropColor, RandomDropNormal |
| 샘플링/크롭 | GridSample, SphereCrop, CropBoundary |
| 인스턴스/사전학습 | InstanceParser, ContrastiveViewsGenerator, MultiViewGenerator |
| 이미지(2D 결합) | ImgToTensor, ImgGaussianBlur, ImgChromaticJitter, ImgPixelContrast, Imgnormalize, ImgRandomHorizontalFlip, ImgRandomResizedCrop, ImgRandomColorJitter, ImgRandomGrayscale, ImgRandomSolarize, ImgAugmentation |
15.3 손실 함수
CrossEntropyLoss(범용 기본), LovaszLoss(mIoU 직접 최적화 — 분할에서 함께 쓰면 효과적), SmoothCELoss, FocalLoss·BinaryFocalLoss(클래스 불균형), DiceLoss
15.4 옵티마이저 / 스케줄러
옵티마이저: SGD, Adam, AdamW(기본 권장), Muon_KIMI
스케줄러: OneCycleLR(기본 권장), CosineAnnealingLR, PolyLR, ExpLR, MultiStepLR, MultiStepWithWarmupLR, CosineScheduler
15.5 훅(hooks)
| 이름 | 역할 |
|---|---|
CheckpointLoader | 시작 시 가중치 로드 (keywords/replacement/strict 지원) |
CheckpointSaver | model_last.pth 저장, 최고 성능이면 model_best.pth 갱신 (save_freq) |
SemSegEvaluator | 매 epoch mIoU/macroF1/mAcc/mPrecision/allAcc 계산 (write_cls_metrics) |
ClsEvaluator / InsSegEvaluator / ShapeNetPartSegEvaluator / PartNetEPartSegEvaluator | 작업별 평가 |
PreciseEvaluator | 학습 종료 후 전체 테스트 자동 실행 (test_last) |
InformationWriter | 로그/텐서보드/wandb 기록 |
IterationTimer | 배치 시간 측정 (warmup_iter) |
ModelHook | 모델 관련 부가 처리 |
DataCacheOperator | 데이터를 공유메모리에 미리 적재 |
RuntimeProfiler / RuntimeProfilerV2 | 성능 프로파일링 |
WeightDecaySchedular | weight decay 스케줄링 |
GarbageHandler | 메모리 정리 |
15.6 Trainer / Tester
Trainer: DefaultTrainer(기본), PartialSampledTrainer, MultiDatasetTrainer(PPT 다중 데이터셋)
Tester: SemSegTester, ClsTester, ClsVotingTester, InsSegTester, ShapeNetPartSegTester, PartNetEPartSegTester, DINOSemSegTester
15.7 데이터셋
DefaultDataset(★내 데이터용), DefaultImagePointDataset, DefaultMultiViewImagePointDataset, ConcatDataset, S3DISDataset, ScanNetDataset, ScanNet200Dataset, ScanNetPPDataset, ScanNetPairDataset, HM3DDataset, Structured3DDataset, AEODataset, SemanticKITTIDataset, SemanticKITTIImagePointDataset, NuScenesDataset, NuScenesImagePointDataset, WaymoDataset, HKDataset, ModelNetDataset, ShapeNetPartDataset, Cap3DDataset, Cap3DImagePointDataset, ScanObjectNNDataset, ScanObjectNNHardestDataset, ScanObjectNNRawDataset, PartNetDataDataset, PartNetEDataset
마지막 체크리스트
data/mydata/{train,val}/장면폴더/coord.npy구조가 맞는가- 좌표 단위가 미터인가 (
coord.max() - coord.min()이 방 크기인 수 미터대인가) - 색상이 0~255인가
- 라벨이
-1또는0..C-1인가 (np.unique로 확인) num_classes가 model·data 양쪽 모두 맞는가,names길이도 같은가Collect.feat_keys의 총 열 수 ==backbone.in_channelsepoch % eval_epoch == 0,batch_size % GPU수 == 0enable_wandb=False(계정 없다면)
더 읽을 것
- Pointcept 공식 저장소 — 모델별 성능 표와 학습 명령이 README에 정리되어 있음
- HuggingFace/Pointcept — 전처리 완료 데이터셋 및 사전학습 가중치
- Sonata 추론 데모 — 학습 없이 3D 특징을 뽑아보는 가장 쉬운 경로
- 저장소 내
README.md의 Model Zoo 절 — 데이터셋 × 모델 조합별 실행 명령 모음