패키지 설치 기록을 실제 실행과 연결한다
로봇 수집·전처리·학습 프로그램은 같은 저장소에 있어도 서로 다른 Python 환경에서 실행될 수 있다. 터미널의 설치 성공 메시지보다 실제 프로세스가 선택한 인터프리터와 import된 모듈의 위치가 중요하다. IDE 실행기, notebook kernel, 서비스가 각각 다른 Python을 선택했다면 한 터미널에서 환경을 활성화한 기록만으로 셋의 환경이 같다고 판정할 수 없다.
Python 3.12 venv 공식 설명에 따르면 가상환경은 기존 Python을 기반으로 프로젝트의 패키지 영역을 분리한다. 임의의 새 Python 버전을 자동 설치하는 도구나 그대로 옮겨 쓸 완전한 시스템 복제본이 아니다. 조사에는 실행 중인 sys.executable, Python 버전, 같은 인터프리터로 확인한 python -m pip --version, 실제 모듈의 설치 위치를 함께 남긴다. 여기의 python은 조사 대상 인터프리터 경로를 뜻하며 명령 이름만 같다는 근거로 판단하지 않는다.
합성 사례에서 실행 경로는 /envA/bin/python인데 패키지 설치 대상은 /envB/lib/python3.12/site-packages라면 우선 서로 다른 환경이라는 가설을 세운다. 환경 B에 다시 설치하는 것은 환경 A의 실행 문제를 해결했다는 증거가 아니다. 프로젝트 파일 이름이 설치 패키지 이름과 같아 로컬 파일을 먼저 import하는 경우도 있으므로 버전 문자열뿐 아니라 실제 모듈 경로를 대조한다. 의도한 경로를 선택한 뒤에는 필요한 작은 import와 입출력 계약을 확인한다. 이 단위에서는 설치나 환경 변경을 요구하지 않는다.
로그를 읽은 뒤 시간과 단위를 검증한다
CSV parser는 표의 필드 경계를 읽지만 값의 물리적 의미까지 보장하지 않는다. 공식 csv 문서의 기본 읽기는 문자열을 반환한다. 입력 헤더가 stamp_s,position_mm이면 이 예제에서는 첫 열을 같은 실행의 상대 시각인 초, 둘째 열을 위치의 밀리미터로 정한다. 출력은 초와 미터이며 250 mm는 250/1000=0.250 m다. 숫자 변환과 단위 변환, 형식 적합성은 별개의 검사다.
다음 계약은 헤더가 다르면 입력 전체를 거부한다. 행의 열 수가 다르거나 숫자가 아니거나 유한하지 않으면 해당 행을 거부하고 행 번호를 남긴다. 상대 시각은 0 이상이어야 하지만 위치는 좌표 기준에 따라 음수일 수 있다. NaN은 숫자 변환을 통과해도 유효한 위치가 아니다. 오류를 0으로 치환하면 결측을 원점 관측으로 바꾸어 이후 속도와 학습 표적을 오염시킨다. 원본과 거부 근거를 보존해야 재검사가 가능하다.
아래 완전한 표준 라이브러리 예제는 언어 문법 연습이 아니라 작은 입출력 계약의 검사기다. 합성 문자열만 읽으며 장치·파일·네트워크를 열지 않는다. 한 줄짜리 레코드에서 헤더를 1행으로 센다. 시간의 엄격한 증가나 센서 교정 정확도는 검사하지 않으므로 이 코드의 통과를 전체 데이터 품질로 확대하지 않는다.
import csv
import io
import math
SAMPLE = "stamp_s,position_mm\n0.000,100\n0.020,110\n0.040,bad\n"
def parse_log(text):
reader = csv.DictReader(io.StringIO(text))
if reader.fieldnames != ["stamp_s", "position_mm"]:
raise ValueError("header must be stamp_s,position_mm")
accepted, rejected = [], []
for line, row in enumerate(reader, start=2):
try:
if set(row) != {"stamp_s", "position_mm"}:
raise ValueError("unexpected columns")
stamp = float(row["stamp_s"])
millimetres = float(row["position_mm"])
if not math.isfinite(stamp) or not math.isfinite(millimetres):
raise ValueError("non-finite value")
if stamp < 0:
raise ValueError("negative relative time")
accepted.append((stamp, millimetres / 1000.0))
except (TypeError, ValueError):
rejected.append(line)
return accepted, rejected
accepted, rejected = parse_log(SAMPLE)
for stamp, metres in accepted:
print(f"{stamp:.3f}s {metres:.3f}m")
print("rejected lines:", rejected)
출력 예는 0.000s 0.100m, 0.020s 0.110m, rejected lines: [4]다. 행 4의 bad를 nan으로 바꾸어도 거부되어야 한다. 음수 상대 시각과 음수 위치를 같은 이유로 거부하지 않는 경계도 확인하자. 실제 파일을 다룰 때는 경로·인코딩·CSV 형식을 추가 계약으로 정한다.
Shape가 맞아도 축 의미가 틀릴 수 있다
NumPy ndarray는 shape가 각 축 길이를, dtype이 원소의 수치 표현을 나타내는 배열이다. AI에서 tensor도 다차원 수치 자료를 표현하지만 어떤 축이 배치·시간·채널인지 자료 자체가 자동 추론해 주지는 않는다. 예를 들어 두 시점의 x/y 위치를 [[100,200],[110,220]] mm로 저장하고 행=시간, 열=x/y라고 정하면 shape는 (2,2)다. 미터 변환 후 둘째 시점은 (0.110,0.220) m다. 행과 열을 뒤집어도 shape가 (2,2)이므로 크기 검사만으로 오류를 찾을 수 없다.
영상도 (H,W,C)와 (C,H,W)는 축의 계약이 다르다. 여기서 H는 높이, W는 너비, C는 채널 수다. 모델이 배치 N을 포함한 (N,C,H,W)를 요구하면 배치 축과 채널 순서를 명시해야 한다. 원소 수가 같다고 reshape만 해서는 채널 축이 올바르게 이동하지 않는다. 축 순서를 바꾸는 연산과 그 뒤의 메모리 배치를 따로 확인한다. 색 순서·단위·정규화 범위도 shape와 독립이다.
NumPy broadcasting 규칙은 뒤쪽 축부터 길이가 같거나 하나가 1이면 연산을 확장한다. 예측 shape (3,1)과 표적 (3,)의 차이는 (3,3)이 될 수 있다. 세 표본을 하나씩 비교하려던 계산이 아홉 쌍 비교가 되는 것이다. 연산 성공은 목적 일치의 판정이 아니다. 기대한 결과 shape를 먼저 적고 실제 결과와 비교한다.
Dtype·device·버퍼 배치도 입력 계약이다
float32는 원소당 4 byte인 32비트 부동소수점 표현이다. (2,2) float32의 원소 자료량은 2×2×4=16 byte이며 Python 객체 등 전체 메모리는 별도다. float64로 바꾸면 같은 원소 수라도 32 byte다. dtype 변환이 mm를 m로 바꾸거나 정수 영상 0~255를 자동으로 모델의 실수 정규화 범위로 바꾸지는 않는다. 예를 들어 128을 float32로 바꾸면 128.0이고, 별도 계약으로 255로 나누어야 약 0.501961이 된다.
PyTorch의 device는 tensor가 놓인 CPU·가속기 등의 위치다. 공식 tensor attributes는 dtype과 device를 별도 속성으로 설명한다. 같은 shape와 dtype이어도 CPU 입력과 가속기 모델의 연산에 필요한 장치 배치는 따로 맞춰야 한다. 전송에는 시간과 저장 공간이 들 수 있으며 dtype 변환과 장치 이동을 같은 작업으로 여기지 않는다. 여기서는 GPU 사용 가능성이나 전송 속도를 측정하지 않는다.
NumPy ndarray의 strides는 각 축의 인덱스를 하나 바꿀 때 이동하는 byte 간격이다. PyTorch tensor.stride는 원소 수 단위이므로 같은 숫자 의미라고 가정하지 말고 소비 API의 규약을 확인한다. 축을 바꾼 view는 원본 버퍼를 공유하면서 연속 배치 조건이 달라질 수 있다. native 소비자가 C 순서 연속 버퍼를 요구하면 shape만 맞추지 말고 그 조건도 확인한다. 복사가 필요한지와 수명·소유권은 다음 단위에서 다룬다. 이 단위의 판정표에는 축 이름·dtype·device·단위·정규화·필요 배치를 함께 적어 숨은 변환을 없앤다.