AI 다이어트 실패 사건 · AI를 다이어트시켰더니 숫자를 못 읽는다 · 이론
AI를 다이어트시켰더니 숫자를 못 읽는다
한 줄 요약
변환 성공, 정수 커널 실행, 분류 품질을 서로 다른 검사로 확인합니다.
왜 이게 필요했나
숫자 판독기의 파일 크기를 줄였는데 정답률이 크게 떨어졌습니다. 변환 명령은 성공했고 파일 확장자도 onnx입니다. 무엇을 근거로 배포를 거절할까요? 파일이 작다는 사실, 정수 가중치가 있다는 사실, 실제 정수 커널이 실행됐다는 사실, 숫자를 제대로 읽는다는 사실은 각각 다른 증거입니다.
어떻게 동작하나
양자화는 실수를 좁은 정수 표현으로 근사하는 변환입니다. scale과 zero point를 이용해 정수와 실수 사이를 대응시키므로 정보 손실이 생길 수 있습니다. 정적 양자화는 미리 보정 입력을 실행해 활성값 범위를 정합니다. QDQ 그래프에는 QuantizeLinear와 DequantizeLinear가 들어갑니다. 이 과정에서는 활성값과 가중치를 signed INT8로 변환하고, 가중치에는 채널별 범위를 사용합니다. 자세한 API는 [ONNX Runtime 공식 안내](https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html)를 참고하세요.
우리 픽스처의 FP32 모델은 세 선형층을 가진 작은 MLP입니다. 보정용 360개 이미지를 읽는 CalibrationDataReader를 만들고 각 표본을 pixels라는 이름의 1×64 float32 입력으로 전달합니다. 변환기는 입력 ONNX 옆에 모양 추론용 임시 파일을 만들 수 있습니다. 따라서 보호된 /opt/lab/quantization/fp32.onnx를 직접 변환하지 않고 자신의 작업 폴더에 source.onnx로 복사해 사용합니다. 원본을 쓰기 가능하게 풀 필요는 없습니다.
제공 bad-calibration.onnx는 모든 픽셀이 0인 보정 입력으로 만든 실패 비교군입니다. 정상 보정 후보와 같은 평가 자료에서 실행한 뒤 차이를 보세요. 어떤 숫자를 다른 숫자로 오인했는지는 혼동행렬에서 드러납니다. 이 교재의 행은 실제 클래스, 열은 예측 클래스입니다. 실제 1을 2로 읽었다면 confusion[1][2]를 늘립니다. 축을 뒤집어도 대각합과 전체 정확도는 같으므로 정확도만 검증하면 오류를 놓칩니다.
마지막 출력에도 QDQ가 적용된 모델은 반올림 때문에 확률 합이 정확히 1이 아닐 수 있습니다. 제공 정상 변환에서 관찰한 합의 최솟값은 약 0.992였습니다. 검사기는 이 변환의 1/255 간격과 클래스 10개의 반올림 오차를 허용하지만 음수·비유한 값·크게 틀어진 합은 거절합니다. 허용 오차가 있다는 말은 아무 출력이나 통과시킨다는 뜻이 아닙니다.
현장에서 만나는 모습
분류기의 배포 검토에 참여했다고 가정해 보세요. 동료가 “24KB 파일을 만들었습니다”라고 말하면 곧바로 반려할 필요는 없습니다. 대신 어떤 입력에서 어떤 성능으로 동작했는지 물으면 됩니다. 파일 크기는 저장 공간의 증거입니다. 가중치 자료형은 표현 방식의 증거입니다. 프로파일은 실행 경로의 증거입니다. 평가 자료에서 얻은 지표는 제한된 범위의 품질 증거입니다. 이 네 질문을 한 항목으로 합치지 않는 것이 이번 과제의 핵심입니다.
혼동행렬은 정확도 숫자를 설명할 수 있게 만들어 줍니다. 실제 정답이 [1, 2, 1, 0, 9]이고 예측이 [2, 2, 1, 0, 8]이라면 맞힌 표본은 세 개입니다. 정확도는 3/5이고, 틀린 두 표본은 1→2와 9→8입니다. 코드가 2→1과 8→9로 기록했다면 비율은 맞아도 보고서의 설명은 거꾸로입니다. summarize 함수를 전체 모델과 분리해 짧은 목록으로 시험하는 이유가 여기에 있습니다.
정확도 차이를 확인할 때 비교 기준도 고정해야 합니다. 기준 FP32 자체가 잘못된 입력을 받으면 후보와 둘 다 낮은 정확도를 내면서 차이만 작아질 수 있습니다. “손실이 작다”는 기준만 두면 둘 다 못 읽는 모델이 통과합니다. 검사기는 제공 FP32가 평가 집합에서 0.95 이상인지 먼저 확인한 뒤 후보와의 차이를 봅니다. 전처리 단독 시험, 기준 모델 확인, 후보 비교가 다른 종류의 오류를 맡습니다.
불량 보정 모델도 실제로 실행해 보는 이유를 생각해 보세요. 보고서의 reject_bad를 항상 true로 쓰는 프로그램은 거절하는 모양을 흉내 낼 뿐입니다. 세 모델의 count·correct·accuracy·혼동행렬을 재계산해 대조해야 불량 후보를 어떤 근거로 거절했는지 알 수 있습니다. 비교군의 모델 크기는 정상 후보와 비슷할 수 있으므로 바이트 수를 대신 품질 지표로 사용하면 안 됩니다. 파일이 작은 것은 이 사건에서 범인이 아니라 별개의 관찰입니다.
CPU 종류가 달라지면 동일 바이트의 INT8 모델에서도 경계에 가까운 표본의 예측이 달라질 수 있습니다. 개발 때 한 기계의 정답 목록을 외워 모든 실행 환경에 적용하지 않습니다. 같은 실행 환경에서 기준과 후보를 함께 평가하고 차이를 검사합니다. 단, 차이가 생겼다는 사실을 무조건 허용하는 것도 아닙니다. 설정한 손실 한도를 넘으면 실패입니다. 바로 뒤 퀴즈에서 오류 방향을 읽고, 마지막 모듈의 통합 실습에서 보고서를 직접 만듭니다.
다음 실습에서 할 것
4–6단계에서 candidate.onnx를 직접 만들고 세 모델의 추론을 실행합니다. metrics.py의 summarize(predictions, labels)는 count·correct·accuracy·10×10 confusion을 반환합니다. 정확도는 0–1 비율입니다. FP32 대비 손실 한도 1%p는 비율 0.01이며, 상대적으로 1% 감소했다는 뜻과 다릅니다. FP32가 0.96이면 후보 0.95가 경계입니다. regression.json에 실제 결과와 불량 모델 거절 판단을 남깁니다.