ultralytics/get_model_erf.py

173 lines
6.7 KiB
Python
Raw Normal View History

2025-02-25 03:58:34 +00:00
import warnings
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')
import torch, yaml, cv2, os, shutil, sys, glob
import numpy as np
np.random.seed(0)
import matplotlib.pyplot as plt
from tqdm import trange
from PIL import Image
from ultralytics.nn.tasks import attempt_load_weights
from timm.utils import AverageMeter
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Times New Roman"
import seaborn as sns
def get_activation(feat, backbone_idx=-1):
def hook(model, inputs, outputs):
if backbone_idx != -1:
for _ in range(5 - len(outputs)): outputs.insert(0, None)
feat.append(outputs[backbone_idx])
else:
feat.append(outputs)
return hook
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
# Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
if not scaleup: # only scale down, do not scale up (for better val mAP)
r = min(r, 1.0)
# Compute padding
ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
if auto: # minimum rectangle
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
elif scaleFill: # stretch
dw, dh = 0.0, 0.0
new_unpad = (new_shape[1], new_shape[0])
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return im, ratio, (dw, dh)
def get_rectangle(data, thresh):
h, w = data.shape
all_sum = np.sum(data)
for i in range(1, h // 2):
selected_area = data[h // 2 - i:h // 2 + 1 + i, w // 2 - i:w // 2 + 1 + i]
area_sum = np.sum(selected_area)
if area_sum / all_sum > thresh:
return i * 2 + 1, (i * 2 + 1) / h * (i * 2 + 1) / w
return None
def heatmap(data, camp='RdYlGn', figsize=(10, 10.75), ax=None, save_path=None):
plt.figure(figsize=figsize, dpi=40)
ax = sns.heatmap(data,
xticklabels=False,
yticklabels=False, cmap=camp,
center=0, annot=False, ax=ax, cbar=True, annot_kws={"size": 24}, fmt='.2f')
plt.tight_layout()
plt.savefig(save_path)
class yolov8_erf:
feature, hooks = [], []
def __init__(self, weight, device, layer, dataset, num_images, save_path) -> None:
device = torch.device(device)
ckpt = torch.load(weight)
model = attempt_load_weights(weight, device)
model.info()
for p in model.parameters():
p.requires_grad_(True)
model.eval()
optimizer = torch.optim.SGD(model.parameters(), lr=0, weight_decay=0)
meter = AverageMeter()
optimizer.zero_grad()
if '-' in layer:
layer_first, layer_second = layer.split('-')
self.hooks.append(model.model[int(layer_first)].register_forward_hook(get_activation(self.feature, backbone_idx=int(layer_second))))
else:
self.hooks.append(model.model[int(layer)].register_forward_hook(get_activation(self.feature)))
self.__dict__.update(locals())
def get_input_grad(self, samples):
_ = self.model(samples)
outputs = self.feature[-1]
self.feature.clear()
out_size = outputs.size()
central_point = torch.nn.functional.relu(outputs[:, :, out_size[2] // 2, out_size[3] // 2]).sum()
grad = torch.autograd.grad(central_point, samples)
grad = grad[0]
grad = torch.nn.functional.relu(grad)
aggregated = grad.sum((0, 1))
grad_map = aggregated.cpu().numpy()
return grad_map
def process(self):
for image_path in os.listdir(self.dataset):
if self.meter.count == self.num_images:
break
img = cv2.imread(f'{self.dataset}/{image_path}')
img = letterbox(img, auto=False)[0]
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.float32(img) / 255.0
samples = torch.from_numpy(np.transpose(img, axes=[2, 0, 1])).unsqueeze(0).to(self.device)
samples.requires_grad = True
self.optimizer.zero_grad()
contribution_scores = self.get_input_grad(samples)
if np.isnan(np.sum(contribution_scores)):
print('got NAN, next image')
continue
else:
print(f'{self.meter.count}/{self.num_images} calculate....')
self.meter.update(contribution_scores)
# Set figure parameters
large = 24; med = 24; small = 24
params = {'axes.titlesize': large,
'legend.fontsize': med,
'figure.figsize': (16, 10),
'axes.labelsize': med,
'xtick.labelsize': med,
'ytick.labelsize': med,
'figure.titlesize': large}
plt.rcParams.update(params)
plt.style.use('seaborn-whitegrid')
sns.set_style("white")
plt.rc('font', **{'family': 'Times New Roman'})
plt.rcParams['axes.unicode_minus'] = False
data = self.meter.avg
print(f'max value:{np.max(data):.3f} min value:{np.min(data):.3f}')
data = np.log10(data + 1) # the scores differ in magnitude. take the logarithm for better readability
data = data / np.max(data) # rescale to [0,1] for the comparability among models
print('======================= the high-contribution area ratio =====================')
for thresh in [0.2, 0.3, 0.5, 0.99]:
side_length, area_ratio = get_rectangle(data, thresh)
print('thresh, rectangle side length, area ratio: ', thresh, side_length, area_ratio)
heatmap(data, save_path=self.save_path)
def get_params():
params = {
'weight': 'yolov8n.pt', # 只需要指定权重即可
'device': 'cuda:0',
'layer': '10', # string
'dataset': '',
'num_images': 50,
'save_path': 'result.png'
}
return params
if __name__ == '__main__':
cfg = get_params()
yolov8_erf(**cfg).process()