DS가 되기 위한 여정 👩‍💻

Projects/Comento_cctv_object_detection

[trouble_shooting] 임베디드를 위한 경량화.. 작업 중

Tashapark 2025. 5. 1. 22:50
728x90

코멘토는 끝났지만,

디벨롭 방향을 고민하다가 

cctv 차량 객체 탐지를 위한 모델인데 

임베디드를 위한 작업은 해본 적이 없어서 

gpt의 추천과 함께 시도를 했다.

 

문제 1)  처음에는 GPT의 추천으로 동적 양자화를 시도했으나,

계속 '모델'을 찾을 수 없다는 에러가 떴다. 

 

모델 생성

mport sys
import torch

# yolov5의 경로를 sys.path에 추가
sys.path.append('/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/')  # yolov5 디렉토리 경로

# YOLOv5 모델 로드
from models.experimental import attempt_load  # YOLOv5 모델 로드 함수

# 1. 학습한 모델 로드 (가중치 파일 경로를 절대 경로로 지정)
model_path = '/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/runs/train/yolov5s_batch64_epoch300/weights/best.pt'  # 학습한 모델 경로
model = attempt_load(model_path, device='cpu')  # 모델 로드, map_location 대신 device='cpu' 사용

# 2. 동적 양자화 적용
# 주로 Linear 레이어에 양자화 적용
quantized_model = torch.quantization.quantize_dynamic(
    model,  # 원본 모델
    {torch.nn.Linear},  # 양자화할 레이어 유형
    dtype=torch.qint8  # 양자화 시 데이터 타입
)

# 3. 양자화된 모델 저장
quantized_model_path = '/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/runs/train/yolov5s_batch64_epoch300/weights/best_quantized.pt'
torch.save(quantized_model.state_dict(), quantized_model_path)

print(f"동적 양자화가 적용된 모델이 {quantized_model_path}에 저장되었습니다.")

 

모델 실행

!python yolov5/val.py --img 224 --batch 60 \
    --data /Users/tasha/Desktop/comento/dataset/datasets/data.yaml \
    --weights /Users/tasha/Desktop/comento/mywork/models/yolov5/runs/train/yolov5s_batch64_epoch300/weights/best_quantized.pt \
    --device cpu
val: data=/Users/tasha/Desktop/comento/dataset/datasets/data.yaml, weights=['yolov5/runs/train/yolov5s_batch64_epoch300/weights/best_quantized.pt'], batch_size=60, imgsz=224, conf_thres=0.001, iou_thres=0.6, max_det=300, task=val, device=cpu, workers=8, single_cls=False, augment=False, verbose=False, save_txt=False, save_hybrid=False, save_conf=False, save_json=False, project=yolov5/runs/val, name=exp, exist_ok=False, half=False, dnn=False
YOLOv5 🚀 v7.0-399-g8cc44963 Python-3.12.9 torch-2.5.0 CPU

Traceback (most recent call last):
  File "/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/val.py", line 604, in <module>
    main(opt)
  File "/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/val.py", line 575, in main
    run(**vars(opt))
  File "/opt/anaconda3/envs/comentoenv/lib/python3.12/site-packages/torch/utils/_contextlib.py", line 116, in decorate_context
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/val.py", line 270, in run
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/models/common.py", line 489, in __init__
    model = attempt_load(weights if isinstance(weights, list) else w, device=device, inplace=True, fuse=fuse)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/models/experimental.py", line 99, in attempt_load
    ckpt = (ckpt.get("ema") or ckpt["model"]).to(device).float()  # FP32 model
                               ~~~~^^^^^^^^^
KeyError: 'model'

 

 

과정)

=> 가장 큰 문제는  끊임 없이 'model'을 찾을 수 없다는 에러가 뜨는 것이었다..

처음에는 계속 파일 경로 확인하고 모델.pt 존재 여부 체크 했는데 그럼에도 계속 에러가 떠서 이게 뭔가 싶었다. 

- 어찌 하다보니, 양자화된 모델은 생성됐는데

문제는 해당 pt를 실행할 수 없는 것이었다 .

- 그래서 계속 돌릴 수 있는 모델을 찾을 수가 없어서 문제가 생긴 것. 

- 그럼에도 해결해보려고, 가능한 모델 가중치 키를 출력하고 해봤으나 (위 코드 해당 에러) 결국 실패하여

- 근본적인 질문을 하기 시작함. 이게 되는 게 맞냐?

=> 처음에는 '체크포인트 로드'가 안 되는 문제라더니.. 결국 아래 답을 알려줌. 

 

❗ 문제 1: torch.quantization.quantize_dynamic()는 YOLOv5 전체 구조에 적합하지 않음

YOLOv5는 복잡한 구조(예: Conv, Detect, Concat, custom module 등)를 포함하고 있어서 quantize_dynamic()이 효과적으로 적용되지 않습니다.
이 함수는 torch.nn.Linear 레이어만 대상으로 하기 때문에, YOLOv5처럼 CNN 기반 모델에는 거의 적용되지 않습니다.

즉, quantize_dynamic()은 자연어 처리 모델(BERT 등)에는 적절하지만, YOLOv5 같은 이미지 처리 모델에는 실질적 효과가 없고, 오히려 비정상적인 상태 저장 구조(state_dict)를 저장해버릴 수 있습니다.

❗ 문제 2: torch.save(quantized_model.state_dict(), ...) 만으로는 모델 복원이 안 됨

YOLOv5의 모델은 구조(.yaml) + 가중치가 필요합니다.
state_dict()만 저장하면 attempt_load()나 DetectMultiBackend()로 다시 로드할 수 없습니다.

 

=====================================> 해결) 양자화 폐기 , MacBook Air M1 으로 CUDA (x)

 

 

문제2) ONNX도 파일 생성은 되는데, yolov5 자체적으로 제공하는 성능 지표처럼 지표확인이 불가. 

경량화 시 성능이 얼마나 떨어지는 것인지 확인해서 비교해보고자  py로 계산 파일 만들었는데도 안 되서 

현재 중지. 

 

 

파일 변환)

import torch

# 모델 로드
model = torch.load('/Users/tasha/Desktop/comento/mywork/models/yolov5/yolov5/runs/train/yolov5s_batch64_epoch300/weights/best.pt', map_location='cpu')['model'].float()
model.eval()

# 더미 입력값 (batch_size=1, 3채널, 640x640 이미지)
dummy_input = torch.randn(1, 3, 640, 640)

# ONNX로 내보내기
torch.onnx.export(model, dummy_input, "best.onnx", 
                  input_names=["images"], output_names=["output"],
                  opset_version=12, export_params=True, do_constant_folding=True)


#실행
from onnxruntime.quantization import quantize_dynamic, QuantType

quantize_dynamic(
    model_input="best.onnx",
    model_output="best_int8.onnx", ## 에러 뜸.
    weight_type=QuantType.QInt8
)

 

---------------------------------------------------------------------------
NotImplemented                            Traceback (most recent call last)
Cell In[55], line 5
      2 import numpy as np
      4 # 세션 생성
----> 5 session = ort.InferenceSession("best_int8.onnx")
      7 # 더미 입력 생성 (예: YOLOv5s는 (1, 3, 640, 640))
      8 dummy_input = np.random.randn(1, 3, 640, 640).astype(np.float32)

File /opt/anaconda3/envs/comentoenv/lib/python3.12/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:469, in InferenceSession.__init__(self, path_or_bytes, sess_options, providers, provider_options, **kwargs)
    466 disabled_optimizers = kwargs.get("disabled_optimizers")
    468 try:
--> 469     self._create_inference_session(providers, provider_options, disabled_optimizers)
    470 except (ValueError, RuntimeError) as e:
    471     if self._enable_fallback:

File /opt/anaconda3/envs/comentoenv/lib/python3.12/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py:541, in InferenceSession._create_inference_session(self, providers, provider_options, disabled_optimizers)
    538     disabled_optimizers = set(disabled_optimizers)
    540 # initialize the C++ InferenceSession
--> 541 sess.initialize_session(providers, provider_options, disabled_optimizers)
    543 self._sess = sess
    544 self._sess_options = self._sess.session_options

NotImplemented: [ONNXRuntimeError] : 9 : NOT_IMPLEMENTED : Could not find an implementation for ConvInteger(10) node with name '/model.0/conv/Conv_quant'

 

 => 문제 2-1)ONNX Runtime이 ConvInteger 연산을 지원하지 않는 경우 발생하는 문제.

- CPU일 경우 일부 int8을 지원하지 않음.

 

quantize_dynamic(
    model_input="best.onnx",
    model_output="best_int8.onnx",
    weight_type=QuantType.QUInt8  # CPU에서 호환성 더 좋음
)

 

=> type 을 QUInt8 이것으로 변경해줌. 

=> 모델 생성은 됐으나,

 

문제 2-2) 성능 비교를 위한 지표 생성에서 막힘. 

 

모델 유형mAP 확인 방법

best.pt ✅ val.py 그대로 사용 가능
best.onnx ❌ val.py 직접 사용 불가 (코드 수정 필요)
best_int8.onnx ❌ val.py 직접 사용 불가 + 연산 제한 주의

 

- 이렇기  때문에 추가 파일을 생성했으나, 연산이 계속 안 됨. 

- 추가 확인이 더 필요함. 

import torch
from utils.metrics import ap_per_class
from sklearn.metrics import precision_score, recall_score, f1_score

# Precision, Recall, F1-Score 계산 함수
def calculate_precision_recall_f1(preds, targets, iou_threshold=0.5):
    # IOU 계산 (예측된 박스와 실제 박스 간의 IOU)
    iou = box_iou(preds[..., :4], targets[..., :4])
    tp = (iou > iou_threshold).float()  # True Positive (TP)
    fp = (iou <= iou_threshold).float()  # False Positive (FP)
    fn = (iou <= iou_threshold).float()  # False Negative (FN)
    
    # Precision, Recall 계산
    precision = tp.sum() / (tp.sum() + fp.sum()) if tp.sum() + fp.sum() > 0 else 0
    recall = tp.sum() / (tp.sum() + fn.sum()) if tp.sum() + fn.sum() > 0 else 0
    
    # F1-Score 계산
    f1 = 2 * (precision * recall) / (precision + recall) if precision + recall > 0 else 0
    
    return precision, recall, f1

# 전체 mAP, Precision, Recall, F1-Score 계산
def evaluate_with_precision_recall(model_path, data_path):
    session = load_onnx_model(model_path)
    dataset = load_dataset(data_path)
    
    all_preds = []
    all_targets = []
    
    precision_list = []
    recall_list = []
    f1_list = []
    
    for img_path, img, target in dataset:
        img = img[None]  # 배치 차원 추가 (1, 3, 640, 640)
        target = target[0]  # target은 배치 크기가 1일 때
        img = img.astype(np.float32) / 255.0  # 정규화

        # 모델 추론
        preds = run_inference(session, img)
        preds = preds[0]  # 첫 번째 출력 (배치 크기가 1이므로)
        
        # NMS 적용
        preds = apply_nms(preds)
        
        # Precision, Recall, F1-Score 계산
        precision, recall, f1 = calculate_precision_recall_f1(preds, target)
        precision_list.append(precision)
        recall_list.append(recall)
        f1_list.append(f1)
        
        # 예측값과 타겟 저장
        all_preds.append(preds)
        all_targets.append(target)
    
    # mAP 계산
    mAP = 0
    for pred, target in zip(all_preds, all_targets):
        mAP += compute_mAP(pred, target)
    
    mAP /= len(all_preds)
    
    # 평균 Precision, Recall, F1-Score 계산
    avg_precision = np.mean(precision_list)
    avg_recall = np.mean(recall_list)
    avg_f1 = np.mean(f1_list)
    
    print(f"mAP: {mAP * 100:.2f}%")
    print(f"Average Precision: {avg_precision * 100:.2f}%")
    print(f"Average Recall: {avg_recall * 100:.2f}%")
    print(f"Average F1-Score: {avg_f1 * 100:.2f}%")

if __name__ == "__main__":
    model_path = "best_int8.onnx"  # 변경: ONNX 모델 경로
    data_path = "/Users/tasha/Desktop/comento/dataset/datasets/data.yaml"  # 변경: 데이터 경로
    evaluate_with_precision_recall(model_path, data_path)

 

- 아래와 같이 원래 yolov5 포맷으로 하고 싶은데 잘 안되는 중임. 

Fusing layers... 
YOLOv5s summary: 157 layers, 7012822 parameters, 0 gradients, 15.8 GFLOPs
val: Scanning /Users/tasha/Desktop/comento/dataset/datasets/labels/val.cache... 
                 Class     Images  Instances          P          R      mAP50   
                   all       1296       3385          1          1      0.995      0.987
Speed: 0.1ms pre-process, 55.5ms inference, 0.4ms NMS per image at shape (60, 3, 224, 224)
Results saved to yolov5/runs/val/exp7

 

 

- 더 해보고 수정 예정. 

728x90
반응형