Spaces:
Build error
Build error
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. | |
from collections import OrderedDict, defaultdict | |
import logging | |
import math | |
import torch | |
from maskrcnn_benchmark.utils.imports import import_file | |
def resize_2d(posemb, shape_new): | |
# Rescale the grid of position embeddings when loading from state_dict. Adapted from | |
# https://github.com/google-research/vision_transformer/blob/00883dd691c63a6830751563748663526e811cee/vit_jax/checkpoint.py#L224 | |
ntok_new = shape_new[0] | |
gs_old = int(math.sqrt(len(posemb))) # 2 * w - 1 | |
gs_new = int(math.sqrt(ntok_new)) # 2 * w - 1 | |
posemb_grid = posemb.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2) | |
posemb_grid = torch.nn.functional.interpolate(posemb_grid, size=(gs_new, gs_new), mode='bilinear') | |
posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(gs_new * gs_new, -1) | |
return posemb_grid | |
def align_and_update_state_dicts(model_state_dict, loaded_state_dict, reshape_keys=['pos_bias_table'], use_weightmap=False): | |
""" | |
Strategy: suppose that the models that we will create will have prefixes appended | |
to each of its keys, for example due to an extra level of nesting that the original | |
pre-trained weights from ImageNet won't contain. For example, model.state_dict() | |
might return backbone[0].body.res2.conv1.weight, while the pre-trained model contains | |
res2.conv1.weight. We thus want to match both parameters together. | |
For that, we look for each model weight, look among all loaded keys if there is one | |
that is a suffix of the current weight name, and use it if that's the case. | |
If multiple matches exist, take the one with longest size | |
of the corresponding name. For example, for the same model as before, the pretrained | |
weight file can contain both res2.conv1.weight, as well as conv1.weight. In this case, | |
we want to match backbone[0].body.conv1.weight to conv1.weight, and | |
backbone[0].body.res2.conv1.weight to res2.conv1.weight. | |
""" | |
current_keys = sorted(list(model_state_dict.keys())) | |
loaded_keys = sorted(list(loaded_state_dict.keys())) | |
# get a matrix of string matches, where each (i, j) entry correspond to the size of the | |
# loaded_key string, if it matches | |
match_matrix = [ | |
len(j) if i.endswith(j) else 0 for i in current_keys for j in loaded_keys | |
] | |
match_matrix = torch.as_tensor(match_matrix).view( | |
len(current_keys), len(loaded_keys) | |
) | |
max_match_size, idxs = match_matrix.max(1) | |
# remove indices that correspond to no-match | |
idxs[max_match_size == 0] = -1 | |
matched_keys = [] | |
# used for logging | |
max_size = max([len(key) for key in current_keys]) if current_keys else 1 | |
max_size_loaded = max([len(key) for key in loaded_keys]) if loaded_keys else 1 | |
log_str_template = "{: <{}} loaded from {: <{}} of shape {}" | |
logger = logging.getLogger(__name__) | |
for idx_new, idx_old in enumerate(idxs.tolist()): | |
if idx_old == -1: | |
continue | |
key = current_keys[idx_new] | |
key_old = loaded_keys[idx_old] | |
if model_state_dict[key].shape != loaded_state_dict[key_old].shape: | |
if any([k in key_old for k in reshape_keys]): | |
new_shape = model_state_dict[key].shape | |
logger.warning('Reshaping {} -> {}. \n'.format(key_old, key)) | |
model_state_dict[key] = resize_2d(loaded_state_dict[key_old], new_shape) | |
elif use_weightmap and 'cls_logits' in key: | |
coco_in_objects365_inds = [ | |
227, 26, 55, 202, 2, 44, 338, 346, 32, 336, 118, 299, 218, | |
25, 361, 59, 95, 161, 278, 82, 110, 22, 364, 134, 9, 350, | |
152, 323, 304, 130, 285, 289, 16, 172, 17, 18, 283, 305, | |
321, 35, 362, 88, 127, 174, 292, 37, 11, 6, 267, 212, 41, | |
58, 162, 237, 98, 48, 63, 81, 247, 23, 94, 326, 349, 178, | |
203, 259, 171, 60, 198, 213, 325, 282, 258, 33, 71, 353, | |
273, 318, 148, 330 | |
] | |
logger.info("Use coco_in_objects365_inds labelmap for COCO detection because of size mis-match, " | |
"Reshaping {} -> {}. \n".format(key_old, key)) | |
new_shape = model_state_dict[key].shape | |
assert new_shape[0] == len(coco_in_objects365_inds) | |
weight_inds_old = torch.as_tensor(coco_in_objects365_inds).to(loaded_state_dict[key_old].device) | |
model_state_dict[key] = loaded_state_dict[key_old][weight_inds_old].to(model_state_dict[key].device) | |
else: | |
logger.info('Skip due to size mismatch: {} -> {}. \n'.format(key_old, key)) | |
continue | |
else: | |
model_state_dict[key] = loaded_state_dict[key_old] | |
matched_keys.append(key) | |
logger.info( | |
log_str_template.format( | |
key, | |
max_size, | |
key_old, | |
max_size_loaded, | |
tuple(loaded_state_dict[key_old].shape), | |
) | |
) | |
missing_keys = set(current_keys)-set(matched_keys) | |
if len(missing_keys): | |
groups = _group_checkpoint_keys(missing_keys) | |
msg_per_group = sorted(k + _group_to_str(v) for k, v in groups.items()) | |
msg = '\n'.join(sorted(msg_per_group)) | |
logger.warning('Some layers unloaded with pre-trained weight: \n' + msg) | |
def strip_prefix_if_present(state_dict, prefix): | |
keys = sorted(state_dict.keys()) | |
if not all(key.startswith(prefix) for key in keys): | |
return state_dict | |
stripped_state_dict = OrderedDict() | |
for key, value in state_dict.items(): | |
stripped_state_dict[key.replace(prefix, "", 1)] = value | |
return stripped_state_dict | |
def load_state_dict(model, loaded_state_dict): | |
model_state_dict = model.state_dict() | |
# if the state_dict comes from a model that was wrapped in a | |
# DataParallel or DistributedDataParallel during serialization, | |
# remove the "module" prefix before performing the matching | |
loaded_state_dict = strip_prefix_if_present(loaded_state_dict, prefix="module.") | |
align_and_update_state_dicts(model_state_dict, loaded_state_dict) | |
# use strict loading | |
model.load_state_dict(model_state_dict) | |
def _group_checkpoint_keys(keys): | |
""" | |
Group keys based on common prefixes. A prefix is the string up to the final | |
"." in each key. | |
Args: | |
keys (list[str]): list of parameter names, i.e. keys in the model | |
checkpoint dict. | |
Returns: | |
dict[list]: keys with common prefixes are grouped into lists. | |
""" | |
groups = defaultdict(list) | |
for key in keys: | |
pos = key.rfind(".") | |
if pos >= 0: | |
head, tail = key[:pos], [key[pos + 1 :]] | |
else: | |
head, tail = key, [] | |
groups[head].extend(tail) | |
return groups | |
def _group_to_str(group): | |
""" | |
Format a group of parameter name suffixes into a loggable string. | |
Args: | |
group (list[str]): list of parameter name suffixes. | |
Returns: | |
str: formated string. | |
""" | |
if len(group) == 0: | |
return "" | |
if len(group) == 1: | |
return "." + group[0] | |
return ".{" + ", ".join(sorted(group)) + "}" |