怎么去跟客戶談網(wǎng)站建設(shè)四川seo優(yōu)化
2.模型標(biāo)定
當(dāng)然可以,模型量化中的標(biāo)定(calibration)是一個關(guān)鍵過程,它主要確保在降低計算精度以減少模型大小和提高推理速度的同時,不會顯著損害模型的準(zhǔn)確性?,F(xiàn)在,我將根據(jù)您提供的步驟解釋這一過程。
1. 收集網(wǎng)絡(luò)層的輸入/輸出信息
首先,我們需要通過運行模型(使用標(biāo)定數(shù)據(jù)集,而不是訓(xùn)練數(shù)據(jù)或測試數(shù)據(jù))來收集關(guān)于每層的輸入和輸出的信息。這個數(shù)據(jù)集應(yīng)該是多樣化的,以便涵蓋到可能的各種情況。
在這一步中,模型是在推理模式下運行的,所有層的輸出都被記錄下來。這通常是通過修改模型的代碼來實現(xiàn)的,以便在每個層之后捕獲并存儲激活的分布。這些數(shù)據(jù)將用于下一步中的統(tǒng)計分析。
具體實施時,這一步可能涉及編寫一個循環(huán),該循環(huán)遍歷標(biāo)定數(shù)據(jù)集的每個樣本,并逐一通過模型。在每一層,您需要捕獲并可能臨時存儲輸入和輸出數(shù)據(jù)(通常是張量的形式)。
def collect_stats(model, data_loader, device, num_batch=200):model.eval() # 將模型設(shè)置為評估(推理)模式。這在PyTorch中很重要,因為某些層(如Dropout和BatchNorm)在訓(xùn)練和評估時有不同的行為。# 開啟校準(zhǔn)器for name, module in model.named_modules(): # 遍歷模型中的所有模塊。`named_modules()`方法提供了一個迭代器,按層次結(jié)構(gòu)列出模型的所有模塊及其名稱。if isinstance(module, quant_nn.TensorQuantizer): # 檢查當(dāng)前模塊是否為TensorQuantizer類型,即我們想要量化的特定類型的層。if module._calibrator is not None: # 如果此層配備了校準(zhǔn)器。module.disable_quant() # 禁用量化。這意味著層將正常(未量化)運行,使校準(zhǔn)器能夠收集必要的統(tǒng)計數(shù)據(jù)。module.enable_calib() # 啟用校準(zhǔn)。這使得校準(zhǔn)器開始在此層的操作期間收集數(shù)據(jù)。else:module.disable() # 如果沒有校準(zhǔn)器,簡單地禁用量化功能,但不進行數(shù)據(jù)收集。# 在此階段,模型準(zhǔn)備好接收數(shù)據(jù),并通過處理未量化的數(shù)據(jù)來進行校準(zhǔn)。# testwith torch.no_grad(): # 關(guān)閉自動求導(dǎo)系統(tǒng)。這在進行推理時是有用的,因為它減少了內(nèi)存使用量,加速了計算,而且我們不需要進行反向傳播。for i, datas in enumerate(data_loader): # 遍歷數(shù)據(jù)加載器。數(shù)據(jù)加載器將提供批量的數(shù)據(jù),通常用于訓(xùn)練或評估。imgs = datas[0].to(device, non_blocking=True).float()/255.0 # 獲取圖像數(shù)據(jù),轉(zhuǎn)換為適當(dāng)?shù)脑O(shè)備(例如GPU),并將其類型轉(zhuǎn)換為float。除以255是常見的歸一化技術(shù),用于將像素值縮放到0到1的范圍。model(imgs) # 用當(dāng)前批次的圖像數(shù)據(jù)執(zhí)行模型推理。if i >= num_batch: # 如果我們已經(jīng)處理了指定數(shù)量的批次,則停止迭代。break# 關(guān)閉校準(zhǔn)器for name, module in model.named_modules(): # 再次遍歷所有模塊,就像我們之前做的那樣。if isinstance(module, quant_nn.TensorQuantizer): # 對于TensorQuantizer類型的模塊。if module._calibrator is not None: # 如果有校準(zhǔn)器。module.enable_quant() # 重新啟用量化。現(xiàn)在,校準(zhǔn)器已經(jīng)收集了足夠的統(tǒng)計數(shù)據(jù),我們可以再次量化層的操作。module.disable_calib() # 禁用校準(zhǔn)。數(shù)據(jù)收集已經(jīng)完成,因此我們關(guān)閉校準(zhǔn)器。else:module.enable() # 如果沒有校準(zhǔn)器,我們只需重新啟用量化功能。# 在此階段,校準(zhǔn)過程完成,模型已經(jīng)準(zhǔn)備好以量化的狀態(tài)進行更高效的運行。
2. 計算動態(tài)范圍和比例因子
一旦我們收集了各層的激活數(shù)據(jù),接下來的步驟是分析這些數(shù)據(jù)來確定量化參數(shù),即動態(tài)范圍(也稱為量化范圍)和比例因子(scale)。
- 動態(tài)范圍是指在量化過程中,張量數(shù)據(jù)可以擴展到的范圍。它是原始數(shù)據(jù)的最大值和最小值之間的差值。這個范圍很重要,因為我們希望我們的量化表示能夠覆蓋可能的所有值,從而避免飽和和信息丟失。
這個過程中,method: A string. One of [‘entropy’, ‘mse’, ‘percentile’] 我們有三種辦法,這個實際上要在做實驗的時候看哪一個精度更高,這個就是看map值計算的區(qū)別
def compute_amax(model, device, **kwargs):# 遍歷模型中的所有模塊,`model.named_modules()`方法提供了一個迭代器,包含模型中所有模塊的名稱和模塊本身。for name, module in model.named_modules():# 檢查當(dāng)前模塊是否為TensorQuantizer的實例,這是處理量化的部分。if isinstance(module, quant_nn.TensorQuantizer):# (這里的print語句已被注釋掉,如果取消注釋,它將打印當(dāng)前處理的模塊的名稱。)# print(name)# 檢查當(dāng)前的量化模塊是否具有校準(zhǔn)器。if module._calibrator is not None:# 如果該模塊的校準(zhǔn)器是MaxCalibrator的實例(一種特定類型的校準(zhǔn)器)...if isinstance(module._calibrator, calib.MaxCalibrator):# ...則調(diào)用load_calib_amax()方法,該方法計算并加載適當(dāng)?shù)?#39;amax'值,它是量化過程中用于縮放的最大激活值。module.load_calib_amax()else:# ...如果校準(zhǔn)器不是MaxCalibrator,我們?nèi)匀徽{(diào)用load_calib_amax方法,但是可以傳遞額外的關(guān)鍵字參數(shù)。# 這些參數(shù)可能會影響'amax'值的計算。module.load_calib_amax(**kwargs) # ['entropy', 'mse', 'percentile'] 這里有三個計算方法,實際過程中要看哪一個比較準(zhǔn),再考慮用哪一個# 將計算出的'amax'值(現(xiàn)在存儲在模塊的'_amax'屬性中)轉(zhuǎn)移到指定的設(shè)備上。# 這確保了與模型數(shù)據(jù)在同一設(shè)備上的'amax'值,這對于后續(xù)的計算步驟(如訓(xùn)練或推理)至關(guān)重要。module._amax = module._amax.to(device)
Scanning '/app/dataset/coco2017/val2017.cache' images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100%|███████████████████████| 5000/5000 [00:00<?, ?it/s]
Origin pth_Model map: Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|█████████████████████████████████████| 625/625 [00:49<00:00, 12.61it/s]all 5000 36781 0.717 0.626 0.675 0.454
Fusing layers...
RepConv.fuse_repvgg_block
RepConv.fuse_repvgg_block
RepConv.fuse_repvgg_block
IDetect.fuse
QDQ auto init map: Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|█████████████████████████████████████| 625/625 [00:39<00:00, 15.78it/s]all 5000 36781 0.718 0.627 0.676 0.455
Calibrate Model map: Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|█████████████████████████████████████| 625/625 [01:42<00:00, 6.09it/s]all 5000 36781 0.73 0.618 0.674 0.454
2.3 完整代碼
import torch
from pytorch_quantization import quant_modules
from models.yolo import Model
from pytorch_quantization.nn.modules import _utils as quant_nn_utils
from pytorch_quantization import calib
import sys
import re
import yaml
import os
os.chdir("/app/bob/yolov7_QAT/yolov7")def load_yolov7_model(weight, device="cpu"):ckpt = torch.load(weight, map_location=device) # 加載模型,模型參數(shù)在哪個設(shè)備上model = Model("cfg/training/yolov7.yaml", ch=3, nc=80).to(device) # 跟yolov7的結(jié)構(gòu),這里沒有包含參數(shù)state_dict = ckpt["model"].float().state_dict() # 從加載的權(quán)重中提取模型的狀態(tài)字典(state_dict), 包含了模型全部的參數(shù),包括卷積權(quán)重等model.load_state_dict(state_dict, strict=False) # 把提取出來的參數(shù)放到y(tǒng)olov7的結(jié)構(gòu)中return model # 返回正確權(quán)重和參數(shù)的模型import collections
from utils.datasets import create_dataloader
def prepare_dataset(cocodir, batch_size=8): dataloader = create_dataloader( # 這里的參數(shù)是跟官網(wǎng)的是一樣的f"{cocodir}/val2017.txt",imgsz=640,batch_size=batch_size,opt=collections.namedtuple("Opt", "single_cls")(False), # collections.namedtuple("Opt", "single_cls")(False)augment=False, hyp=None, rect=True, cache=False, stride=32, pad=0.5, image_weights=False,)[0]return dataloaderimport test as test
from pathlib import Path
import os
def evaluate_coco(model, loader, save_dir='.', conf_thres=0.001, iou_thres=0.65):if save_dir and os.path.dirname(save_dir) != "":os.makedirs(os.path.dirname(save_dir), exist_ok=True)return test.test("./data/coco.yaml",save_dir=Path(save_dir),conf_thres=conf_thres,iou_thres=iou_thres,model=model,dataloader=loader,is_coco=True,plots=False,half_precision=True,save_json=False)[0][3] from pytorch_quantization import nn as quant_nn
from pytorch_quantization.tensor_quant import QuantDescriptor
from absl import logging as quant_logging
# intput QuantDescriptor: Max ==> Histogram
def initialize():quant_desc_input = QuantDescriptor(calib_method="histogram") # "max" quant_nn.QuantConv2d.set_default_quant_desc_input(quant_desc_input)quant_nn.QuantMaxPool2d.set_default_quant_desc_input(quant_desc_input)quant_nn.QuantLinear.set_default_quant_desc_input(quant_desc_input)quant_logging.set_verbosity(quant_logging.ERROR) def prepare_model(weight, device):# quant_modules.initialize() # 自動加載qdq節(jié)點initialize() # intput QuantDescriptor: Max ==> Histogrammodel = load_yolov7_model(weight, device)model.float()model.eval()with torch.no_grad():model.fuse() # conv bn 進行層的合并, 加速return model# 執(zhí)行量化替換
def transfer_torch_to_quantization(nn_instance, quant_mudule):quant_instance = quant_mudule.__new__(quant_mudule)for k, val in vars(nn_instance).items():setattr(quant_instance, k, val)def __init__(self):# 返回兩個QuantDescriptor的實例 self.__class__是quant_instance的類, EX: QuantConv2dquant_desc_input, quant_desc_weight = quant_nn_utils.pop_quant_desc_in_kwargs(self.__class__)if isinstance(self, quant_nn_utils.QuantInputMixin):self.init_quantizer(quant_desc_input)if isinstance(self._input_quantizer._calibrator, calib.HistogramCalibrator):self._input_quantizer._calibrator._torch_hist = Trueelse:self.init_quantizer(quant_desc_input, quant_desc_weight)if isinstance(self._input_quantizer._calibrator, calib.HistogramCalibrator):self._input_quantizer._calibrator._torch_hist = Trueself._weight_quantizer._calibrator._torch_hist = True__init__(quant_instance)return quant_instancedef quantization_ignore_match(ignore_layer, path):if ignore_layer is None:return Falseif isinstance(ignore_layer, str) or isinstance(ignore_layer, list):if isinstance(ignore_layer, str):ignore_layer = [ignore_layer]if path in ignore_layer:return Truefor item in ignore_layer:if re.match(item, path): return True return False# 遞歸函數(shù)
def torch_module_find_quant_module(module, module_dict, ignore_layer, prefix=''):for name in module._modules:submodule = module._modules[name]path = name if prefix == '' else prefix + '.' + nametorch_module_find_quant_module(submodule, module_dict, ignore_layer, prefix=path)submodule_id = id(type(submodule))if submodule_id in module_dict:ignored = quantization_ignore_match(ignore_layer, path)if ignored:print(f"Quantization : {path} has ignored.")continue# 轉(zhuǎn)換module._modules[name] = transfer_torch_to_quantization(submodule, module_dict[submodule_id])# 用量化模型替換
def replace_to_quantization_model(model, ignore_layer=None):"""這里構(gòu)建的module_dict里面的元素是一個映射的關(guān)系, 例如torch.nn -> quant_nn.QuantConv2d, 一共是15個, 跟DEFAULT_QUANT_MAP對齊"""module_dict = {}for entry in quant_modules._DEFAULT_QUANT_MAP: # 構(gòu)建module_dict, 把DEFAULT_QUANT_MAP填充module = getattr(entry.orig_mod, entry.mod_name) # 提取的原始的模塊,從torch.nn中獲取conv2d這個字符串module_dict[id(module)] = entry.replace_mod # 使用替換的模塊torch_module_find_quant_module(model, module_dict, ignore_layer)def collect_stats(model, data_loader, device, num_batch=200):model.eval() # 將模型設(shè)置為評估(推理)模式。這在PyTorch中很重要,因為某些層(如Dropout和BatchNorm)在訓(xùn)練和評估時有不同的行為。# 開啟校準(zhǔn)器for name, module in model.named_modules(): # 遍歷模型中的所有模塊。`named_modules()`方法提供了一個迭代器,按層次結(jié)構(gòu)列出模型的所有模塊及其名稱。if isinstance(module, quant_nn.TensorQuantizer): # 檢查當(dāng)前模塊是否為TensorQuantizer類型,即我們想要量化的特定類型的層。if module._calibrator is not None: # 如果此層配備了校準(zhǔn)器。module.disable_quant() # 禁用量化。這意味著層將正常(未量化)運行,使校準(zhǔn)器能夠收集必要的統(tǒng)計數(shù)據(jù)。module.enable_calib() # 啟用校準(zhǔn)。這使得校準(zhǔn)器開始在此層的操作期間收集數(shù)據(jù)。else:module.disable() # 如果沒有校準(zhǔn)器,簡單地禁用量化功能,但不進行數(shù)據(jù)收集。# 在此階段,模型準(zhǔn)備好接收數(shù)據(jù),并通過處理未量化的數(shù)據(jù)來進行校準(zhǔn)。# testwith torch.no_grad(): # 關(guān)閉自動求導(dǎo)系統(tǒng)。這在進行推理時是有用的,因為它減少了內(nèi)存使用量,加速了計算,而且我們不需要進行反向傳播。for i, datas in enumerate(data_loader): # 遍歷數(shù)據(jù)加載器。數(shù)據(jù)加載器將提供批量的數(shù)據(jù),通常用于訓(xùn)練或評估。imgs = datas[0].to(device, non_blocking=True).float()/255.0 # 獲取圖像數(shù)據(jù),轉(zhuǎn)換為適當(dāng)?shù)脑O(shè)備(例如GPU),并將其類型轉(zhuǎn)換為float。除以255是常見的歸一化技術(shù),用于將像素值縮放到0到1的范圍。model(imgs) # 用當(dāng)前批次的圖像數(shù)據(jù)執(zhí)行模型推理。if i >= num_batch: # 如果我們已經(jīng)處理了指定數(shù)量的批次,則停止迭代。break# 關(guān)閉校準(zhǔn)器for name, module in model.named_modules(): # 再次遍歷所有模塊,就像我們之前做的那樣。if isinstance(module, quant_nn.TensorQuantizer): # 對于TensorQuantizer類型的模塊。if module._calibrator is not None: # 如果有校準(zhǔn)器。module.enable_quant() # 重新啟用量化。現(xiàn)在,校準(zhǔn)器已經(jīng)收集了足夠的統(tǒng)計數(shù)據(jù),我們可以再次量化層的操作。module.disable_calib() # 禁用校準(zhǔn)。數(shù)據(jù)收集已經(jīng)完成,因此我們關(guān)閉校準(zhǔn)器。else:module.enable() # 如果沒有校準(zhǔn)器,我們只需重新啟用量化功能。# 在此階段,校準(zhǔn)過程完成,模型已經(jīng)準(zhǔn)備好以量化的狀態(tài)進行更高效的運行。def compute_amax(model, device, **kwargs):# 遍歷模型中的所有模塊,`model.named_modules()`方法提供了一個迭代器,包含模型中所有模塊的名稱和模塊本身。for name, module in model.named_modules():# 檢查當(dāng)前模塊是否為TensorQuantizer的實例,這是處理量化的部分。if isinstance(module, quant_nn.TensorQuantizer):# (這里的print語句已被注釋掉,如果取消注釋,它將打印當(dāng)前處理的模塊的名稱。)# print(name)# 檢查當(dāng)前的量化模塊是否具有校準(zhǔn)器。if module._calibrator is not None:# 如果該模塊的校準(zhǔn)器是MaxCalibrator的實例(一種特定類型的校準(zhǔn)器)...if isinstance(module._calibrator, calib.MaxCalibrator):# ...則調(diào)用load_calib_amax()方法,該方法計算并加載適當(dāng)?shù)?#39;amax'值,它是量化過程中用于縮放的最大激活值。module.load_calib_amax()else:# ...如果校準(zhǔn)器不是MaxCalibrator,我們?nèi)匀徽{(diào)用load_calib_amax方法,但是可以傳遞額外的關(guān)鍵字參數(shù)。# 這些參數(shù)可能會影響'amax'值的計算。module.load_calib_amax(**kwargs) # ['entropy', 'mse', 'percentile'] 這里有三個計算方法,實際過程中要看哪一個比較準(zhǔn),再考慮用哪一個# 將計算出的'amax'值(現(xiàn)在存儲在模塊的'_amax'屬性中)轉(zhuǎn)移到指定的設(shè)備上。# 這確保了與模型數(shù)據(jù)在同一設(shè)備上的'amax'值,這對于后續(xù)的計算步驟(如訓(xùn)練或推理)至關(guān)重要。module._amax = module._amax.to(device)def calibrate_model(model, dataloader, device):# 收集信息collect_stats(model, dataloader, device)# 獲取動態(tài)范圍,計算amax值,scale值compute_amax(model, device, method='mse')if __name__ == "__main__":weight = "./yolov7.pt"cocodir = "/app/dataset/coco2017" #../dataset/coco2017device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")# load最初版本的模型pth_model = load_yolov7_model(weight=weight, device=device)# print(pth_model)dataloader = prepare_dataset(cocodir=cocodir, )print("Origin pth_Model map: ")ap = evaluate_coco(pth_model, dataloader)# 加載自動插入QDQ節(jié)點的模型# print("Before prepare_model")model = prepare_model(weight=weight, device=device)# print("After prepare_model")print("QDQ auto init map: ")qdq_auto_ap = evaluate_coco(model, dataloader)# print("Before replace_to_quantization_model")replace_to_quantization_model(model)# print("After replace_to_quantization_model")# print("Before calibrate_model")calibrate_model(model, dataloader, device)# print("After calibrate_model")print("Calibrate Model map: ")cali_ap = evaluate_coco(model, dataloader)