Pointcept 실전 가이드

딥러닝 지식이 없는 Python 개발자가 3D 포인트 클라우드 AI 모델을 학습·평가·추론하기까지

기준 저장소: /home/zer0/Projects/Pointcept · 작성일 2026-08-26 · 설정 파일 292개 · 등록 모델 55개+ · 데이터 변환 44종

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/<데이터셋>/<실험이름>/이 생기고 로그·체크포인트·설정 사본·예측 결과가 전부 그 안에 모입니다.

이 문서를 읽는 순서 추천 처음이라면 1 → 3 → 4 → 8 → 9만 읽고 공개 데이터셋으로 한 번 돌려보세요. 내 데이터를 쓰려면 7.2, 학습된 모델을 서비스 코드에 붙이려면 10으로 바로 가면 됩니다.

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
가장 흔한 출발점 대부분의 실무 요구는 semantic segmentation 하나로 해결됩니다. “점마다 이게 뭔지 알고 싶다”면 semseg-pt-v3m1-0-base부터 시작하세요. PTv3는 이 저장소에서 실내·실외 모두 가장 성능이 좋은 기본 백본입니다.

2.2 지원 모델 (백본)

백본(backbone)은 “점 → 특징 벡터”를 만드는 본체 신경망입니다. 설정 파일의 model.backbone.type 문자열만 바꾸면 교체됩니다.

type 문자열모델특징 / 언제 쓰나추가 설치
PT-v3m1Point Transformer V3기본 추천. 정확도·속도 모두 최상급. 실내/실외 공통flash-attn (선택, 없으면 enable_flash=False)
PT-v3m2 / PT-v3m3PTv3 (Sonata / Utonia 변형)사전학습 프레임워크 전용 변형동일
SpUNet-v1m1Sparse UNet (SpConv)가볍고 안정적. 메모리 여유가 적을 때 / 베이스라인spconv-cu124
SpUNet-v1m3SpUNet + PDNorm여러 데이터셋 공동학습(PPT)용동일
MinkUNet34CMinkowskiEngine UNet논문 재현용. 설치가 까다로워 SpUNet 권장MinkowskiEngine
PT-v2m2Point Transformer V2PTv3 이전 세대. 24GB GPU에서 동작libs/pointops
PointTransformer-Seg50Point Transformer V1역사적 베이스라인libs/pointops
OACNNsOA-CNNs희소 CNN 계열, 실내 분할에 강함spconv
SPVCNNSPVCNN실외 LiDAR (SemanticKITTI)torchsparse
OctFormer-v1m1OctFormer옥트리 기반 트랜스포머ocnn-pytorch, dwconv
ST-v1m2Stratified Transformertorch-points3d 계열
Swin3D-v1m1Swin3DStructured3D 사전학습 조합에 강함MinkowskiEngine + Swin3D
(기본 __init__.py에서 주석 처리됨)
LitePT-v1LitePT경량 모델. 추론 비용 민감할 때
PG-v1m1 / SGIFormer-v1m1PointGroup / SGIFormer인스턴스 분할 전용libs/pointgroup_ops
MSC-v1m1, Sonata-v1m1, Concerto-v1m1, Utonia-v1m1, PPT-v1m1사전학습 프레임워크백본을 감싸서 라벨 없이 학습시키는 “래퍼”
주의: 추가 설치가 필요한 모델 MinkUNet*Swin3D-v1m1은 기본 상태에서 import가 주석 처리되어 있습니다. 쓰려면 pointcept/models/__init__.pypointcept/models/sparse_unet/__init__.py의 해당 줄 주석을 풀고 MinkowskiEngine을 설치해야 합니다. 처음이라면 PT-v3m1 또는 SpUNet-v1m1만 쓰는 편이 훨씬 편합니다.

2.3 지원 데이터셋

데이터셋종류dataset_typeconfigs 폴더전처리 스크립트
ScanNet v2실내 RGB-D 스캔 (20 클래스)ScanNetDatasetconfigs/scannet/ (43개)preprocessing/scannet/preprocess_scannet.py
ScanNet200실내, 200 클래스 (long-tail)ScanNet200Datasetconfigs/scannet200/ (15개)동일
ScanNet++고해상도 실내 스캔ScanNetPPDatasetconfigs/scannetpp/ (18개)preprocessing/scannetpp/
S3DIS실내 6개 구역 (13 클래스)S3DISDatasetconfigs/s3dis/ (18개)preprocessing/s3dis/preprocess_s3dis.py
Structured3D합성 실내 (사전학습용 대량 데이터)Structured3DDatasetconfigs/structured3d/ (7개)preprocessing/structured3d/
Matterport3D / HM3D대규모 실내HM3DDatasetconfigs/matterport3d/preprocessing/matterport3d/, preprocessing/hm3d/
SemanticKITTI실외 LiDAR (자율주행)SemanticKITTIDatasetconfigs/semantic_kitti/ (8개)불필요 (원본 직접 로드)
nuScenes실외 LiDARNuScenesDatasetconfigs/nuscenes/ (8개)preprocessing/nuscenes/
Waymo실외 LiDARWaymoDatasetconfigs/waymo/ (3개)preprocessing/waymo/
ModelNet40단일 물체 형상 분류 (40 클래스)ModelNetDatasetconfigs/modelnet40/ (2개)불필요
ScanObjectNN실제 스캔 물체 분류ScanObjectNNDataset불필요
ShapeNetPart / PartNetE부위 분할ShapeNetPartDataset, PartNetEDatasetpreprocessing/partnete/
내 데이터임의DefaultDataset직접 작성7.2절 참고
전처리를 건너뛰는 지름길 Pointcept 팀이 전처리 완료본을 HuggingFace (huggingface.co/Pointcept)에 올려두었습니다. 원본 데이터셋 신청·다운로드·변환 과정을 건너뛰고 바로 학습을 돌려볼 수 있습니다.

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
criterialoss를 계산하는 함수 목록. 여러 개를 섞어 씁니다.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)
offsetPointcept 고유 개념. 여러 장면을 하나로 이어 붙인 뒤 “어디서 끊기는지” 표시하는 누적합 배열. 장면 A가 100점, B가 250점이면 offset=[100, 350]. PyTorch Geometric의 batch와 같은 역할.모든 모델 입력
mIoU주 평가 지표. 클래스별 (교집합/합집합) 비율의 평균. 0~1, 높을수록 좋음. 벽/바닥처럼 흔한 클래스에 점수가 쏠리지 않게 보정된 지표.Val result: mIoU/...
mAcc / allAcc클래스별 정확도의 평균 / 전체 점 기준 정확도. allAcc는 흔한 클래스에 유리해서 실제 품질을 과대평가합니다.같은 로그 줄
TTATest Time Augmentation. 테스트할 때 같은 장면을 회전·확대해 여러 번 예측하고 평균 냄. 정확도 ↑, 속도 ↓ (12배 이상 느려질 수 있음).test_cfg.aug_transform
AMPMixed 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설치

요구 사항

방법 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
가장 흔한 설치 실패 3가지
  • flash-attn 빌드 실패 → 설치를 건너뛰고 설정에서 model.backbone.enable_flash=False로 두면 동작합니다(조금 느림).
  • spconv / CUDA 버전 불일치spconv-cu124처럼 CUDA 버전 접미사가 nvcc --version과 맞는지 확인.
  • ModuleNotFoundError: pointceptexport 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__ 시그니처를 열어보면 끝입니다.

레지스트리는 종류별로 나뉘어 있습니다.

레지스트리설정 위치정의된 곳
MODELSmodel, model.backbonepointcept/models/
DATASETSdata.train/val/testpointcept/datasets/
TRANSFORMSdata.*.transform 리스트pointcept/datasets/transform.py
LOSSESmodel.criteriapointcept/models/losses/
OPTIMIZERS / SCHEDULERSoptimizer, schedulerpointcept/utils/optimizer.py, scheduler.py
HOOKShooks 리스트pointcept/engines/hooks/
TRAINERS / TESTERStrain, testpointcept/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=[...])),
)
★ 표시가 내 데이터를 쓸 때 반드시 바꿔야 하는 5곳 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)]],
        ),
    ),
)
Collectin_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 mydataconfigs/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
-gGPU 개수자동 감지-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_sizenum_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장 / 12GBbatch_size=2 num_worker=4 enable_amp=True + grid_size=0.05
GPU 1장 / 24GBbatch_size=4 num_worker=8 enable_amp=True
GPU 4장 / 24GBbatch_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

-wexp/<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)
추론 시 반드시 학습과 맞춰야 하는 것
  1. feat_keys — 학습 설정의 Collect(feat_keys=...)와 완전히 동일한 순서·구성이어야 합니다. color만으로 학습했으면 추론도 color만.
  2. grid_size — 학습 때 값과 같아야 합니다. 다르면 성능이 크게 떨어집니다.
  3. 입력 스케일 — 좌표는 미터, 색은 0~255.
  4. 설정 파일은 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 · 추론 데모 저장소
Concerto2D-3D 결합 자기지도. 공간 표현 학습huggingface.co/Pointcept/Concerto
Utonia“모든 포인트 클라우드를 위한 하나의 인코더” — 최신huggingface.co/Pointcept/Utonia · 추론 저장소
PPT여러 데이터셋 공동학습 가중치Pointcept README 모델 주
가장 빠른 길 “내 스캔에서 뭔가 의미 있는 걸 뽑아보고 싶다”가 목표라면, Pointcept으로 학습하기 전에 Sonata / Utonia의 전용 추론 저장소를 먼저 써보세요. 데모 스크립트 한 줄로 특징 추출·시각화가 되며, 학습 인프라가 필요 없습니다.

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.names3곳 모두
학습 시간 단축epoch를 줄이고 eval_epoch를 같은 값 또는 약수로
GPU 메모리 부족batch_size ↓ ② enable_amp=TrueGridSample.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 리스트. 클래스 불균형이 심하면 FocalLossLovaszLoss 가중치 ↑
특정 클래스를 학습에서 제외해당 점의 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 체크포인트 저장hooksCheckpointSaver(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.shulimit -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 filesnum_worker 과다 + ulimit 낮음ulimit -n 65536
ImportError: flash_attnflash-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.shPyTorch/CUDA 버전을 지정해 Docker 이미지를 빌드sh scripts/build_image.sh -t 2.5.0 -c 12.4 --cudnn 9
scripts/build_wheels.shCUDA 확장(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 지원)
CheckpointSavermodel_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성능 프로파일링
WeightDecaySchedularweight 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


마지막 체크리스트

내 데이터로 처음 학습을 돌리기 전에 확인할 8가지
  1. data/mydata/{train,val}/장면폴더/coord.npy 구조가 맞는가
  2. 좌표 단위가 미터인가 (coord.max() - coord.min()이 방 크기인 수 미터대인가)
  3. 색상이 0~255인가
  4. 라벨이 -1 또는 0..C-1인가 (np.unique로 확인)
  5. num_classes가 model·data 양쪽 모두 맞는가, names 길이도 같은가
  6. Collect.feat_keys의 총 열 수 == backbone.in_channels
  7. epoch % eval_epoch == 0, batch_size % GPU수 == 0
  8. enable_wandb=False (계정 없다면)

더 읽을 것