make_model.py
import torch
import torch.nn as nn
import numpy as np
from .clip.simple_tokenizer import SimpleTokenizer as _Tokenizer
_tokenizer = _Tokenizer() # 初始化一个简单的分词器实例
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
# 定义一种初始化神经网络权重的函数
def weights_init_kaiming(m):
classname = m.__class__.__name__
if classname.find('Linear') != -1:
# 如果是全连接层,使用kaiming_normal初始化权重,偏置设为0
nn.init.kaiming_normal_(m.weight, a=0, mode='fan_out')
nn.init.constant_(m.bias, 0.0)
elif classname.find('Conv') != -1:
# 如果是卷积层,使用kaiming_normal初始化权重,偏置设为0
nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in')
if m.bias is not None:
nn.init.constant_(m.bias, 0.0)
elif classname.find('BatchNorm') != -1:
# 如果是批量归一化层,权重设为1,偏置设为0
if m.affine:
nn.init.constant_(m.weight, 1.0)
nn.init.constant_(m.bias, 0.0)
# 定义一种初始化分类器权重的函数
def weights_init_classifier(m):
classname = m.__class__.__name__
if classname.find('Linear') != -1:
# 对全连接层权重使用标准正态分布初始化,偏置设为0
nn.init.normal_(m.weight, std=0.001)
if m.bias:
nn.init.constant_(m.bias, 0.0)
# 定义Transformer模型构建类
class build_transformer(nn.Module):
def __init__(self, num_classes, camera_num, view_num, cfg):
super(build_transformer, self).__init__()
self.model_name = cfg.MODEL.NAME
self.cos_layer = cfg.MODEL.COS_LAYER
self.neck = cfg.MODEL.NECK
self.neck_feat = cfg.TEST.NECK_FEAT
# 根据模型名称设置输入特征维度
if self.model_name == 'ViT-B-16':
self.in_planes = 768
self.in_planes_proj = 512
elif self.model_name == 'RN50':
self.in_planes = 2048
self.in_planes_proj = 1024
self.num_classes = num_classes
self.camera_num = camera_num
self.view_num = view_num
self.sie_coe = cfg.MODEL.SIE_COE
# 初始化分类器
self.classifier = nn.Linear(self.in_planes, self.num_classes, bias=False)
self.classifier.apply(weights_init_classifier)
self.classifier_proj = nn.Linear(self.in_planes_proj, self.num_classes, bias=False)
self.classifier_proj.apply(weights_init_classifier)
# 初始化瓶颈层(BatchNorm)
self.bottleneck = nn.BatchNorm1d(self.in_planes)
self.bottleneck.bias.requires_grad_(False) # 不对BN层的偏置进行反向传播
self.bottleneck.apply(weights_init_kaiming)
self.bottleneck_proj = nn.BatchNorm1d(self.in_planes_proj)
self.bottleneck_proj.bias.requires_grad_(False)
self.bottleneck_proj.apply(weights_init_kaiming)
# 计算图像分辨率
self.h_resolution = int((cfg.INPUT.SIZE_TRAIN[0] - 16) // cfg.MODEL.STRIDE_SIZE[0] + 1)
self.w_resolution = int((cfg.INPUT.SIZE_TRAIN[1] - 16) // cfg.MODEL.STRIDE_SIZE[1] + 1)
self.vision_stride_size = cfg.MODEL.STRIDE_SIZE[0]
# 加载 CLIP 模型
clip_model = load_clip_to_cpu(self.model_name, self.h_resolution, self.w_resolution, self.vision_stride_size)
clip_model.to("cuda") # 将 CLIP 模型加载到 GPU 上
self.image_encoder = clip_model.visual
# 初始化 SIE 参数
if cfg.MODEL.SIE_CAMERA and cfg.MODEL.SIE_VIEW:
self.cv_embed = nn.Parameter(torch.zeros(camera_num * view_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('camera number is : {}'.format(camera_num))
elif cfg.MODEL.SIE_CAMERA:
self.cv_embed = nn.Parameter(torch.zeros(camera_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('camera number is : {}'.format(camera_num))
elif cfg.MODEL.SIE_VIEW:
self.cv_embed = nn.Parameter(torch.zeros(view_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('camera number is : {}'.format(view_num))
def forward(self, x, label=None, cam_label=None, view_label=None):
# 前向传播,基于不同的模型名称处理输入数据
if self.model_name == 'RN50':
image_features_last, image_features, image_features_proj = self.image_encoder(x)
img_feature_last = nn.functional.avg_pool2d(image_features_last, image_features_last.shape[2:4]).view(x.shape[0], -1)
img_feature = nn.functional.avg_pool2d(image_features, image_features.shape[2:4]).view(x.shape[0], -1)
img_feature_proj = image_features_proj[0]
elif self.model_name == 'ViT-B-16':
if cam_label is not None and view_label is not None:
cv_embed = self.sie_coe * self.cv_embed[cam_label * self.view_num + view_label]
elif cam_label is not None:
cv_embed = self.sie_coe * self.cv_embed[cam_label]
elif view_label is not None:
cv_embed = self.sie_coe * self.cv_embed[view_label]
else:
cv_embed = None
image_features_last, image_features, image_features_proj = self.image_encoder(x, cv_embed)
img_feature_last = image_features_last[:, 0]
img_feature = image_features[:, 0]
img_feature_proj = image_features_proj[:, 0]
# 瓶颈层的特征
feat = self.bottleneck(img_feature)
feat_proj = self.bottleneck_proj(img_feature_proj)
if self.training:
# 训练阶段,返回分类器得分和特征
cls_score = self.classifier(feat)
cls_score_proj = self.classifier_proj(feat_proj)
return [cls_score, cls_score_proj], [img_feature_last, img_feature, img_feature_proj]
else:
# 测试阶段,根据neck_feat参数决定返回的特征
if self.neck_feat == 'after':
return torch.cat([feat, feat_proj], dim=1)
else:
return torch.cat([img_feature, img_feature_proj], dim=1)
def load_param(self, trained_path):
# 从指定路径加载预训练模型参数
param_dict = torch.load(trained_path)
for i in param_dict:
self.state_dict()[i.replace('module.', '')].copy_(param_dict[i])
print('Loading pretrained model from {}'.format(trained_path))
def load_param_finetune(self, model_path):
# 从指定路径加载微调模型参数
param_dict = torch.load(model_path)
for i in param_dict:
self.state_dict()[i].copy_(param_dict[i])
print('Loading pretrained model for finetuning from {}'.format(model_path))
def make_model(cfg, num_class, camera_num, view_num):
# 创建并返回一个build_transformer模型实例
model = build_transformer(num_class, camera_num, view_num, cfg)
return model
from .clip import clip
def load_clip_to_cpu(backbone_name, h_resolution, w_resolution, vision_stride_size):
# 从CLIP模型下载并加载到CPU
url = clip._MODELS[backbone_name]
model_path = clip._download(url)
try:
model = torch.jit.load(model_path, map_location="cpu").eval()
state_dict = None
except RuntimeError:
state_dict = torch.load(model_path, map_location="cpu")
model = clip.build_model(state_dict or model.state_dict(), h_resolution, w_resolution, vision_stride_size)
return model
代码注释
-
导入库和初始化:
import torch
和import torch.nn as nn
:导入 PyTorch 和神经网络模块。import numpy as np
:导入 NumPy,用于数值计算。from .clip.simple_tokenizer import SimpleTokenizer as _Tokenizer
:从本地模块导入简单的分词器。from timm.models.layers import DropPath, to_2tuple, trunc_normal_
:从 timm 库导入模型层的工具函数。
-
权重初始化函数:
weights_init_kaiming
和weights_init_classifier
:定义了两种权重初始化方式,分别用于普通层和分类层。
-
Transformer 模型构建类:
class build_transformer(nn.Module)
:定义了一个继承自nn.Module
的类,用于构建 Transformer 模型。__init__
:构造函数,初始化模型的各个组件,包括分类器、瓶颈层和 CLIP 模型。- 根据模型名称初始化不同的参数。
- 初始化分类器和瓶颈层,并应用相应的初始化函数。
- 加载 CLIP 模型到 GPU,并提取图像编码器。
- 初始化 SIE 参数(相机和视角)
- load_param:从指定路径加载预训练模型参数。
- load_param_finetune:从指定路径加载微调模型参数。
-
辅助函数:
make_model
:创建并返回一个 build_transformer 模型实例。load_clip_to_cpu
:从 CLIP 模型下载并加载到 CPU 上。
make_model_clipreid.py
import torch
import torch.nn as nn
import numpy as np
from .clip.simple_tokenizer import SimpleTokenizer as _Tokenizer
_tokenizer = _Tokenizer()
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
# 权重初始化函数,使用Kaiming方法初始化线性层、卷积层和BatchNorm层的权重
def weights_init_kaiming(m):
classname = m.__class__.__name__
if classname.find('Linear') != -1:
nn.init.kaiming_normal_(m.weight, a=0, mode='fan_out')
nn.init.constant_(m.bias, 0.0)
elif classname.find('Conv') != -1:
nn.init.kaiming_normal_(m.weight, a=0, mode='fan_in')
if m.bias is not None:
nn.init.constant_(m.bias, 0.0)
elif classname.find('BatchNorm') != -1:
if m.affine:
nn.init.constant_(m.weight, 1.0)
nn.init.constant_(m.bias, 0.0)
# 分类器权重初始化函数
def weights_init_classifier(m):
classname = m.__class__.__name__
if classname.find('Linear') != -1:
nn.init.normal_(m.weight, std=0.001)
if m.bias:
nn.init.constant_(m.bias, 0.0)
# 文本编码器类,使用CLIP模型的Transformer和投影层来编码文本
class TextEncoder(nn.Module):
def __init__(self, clip_model):
super().__init__()
self.transformer = clip_model.transformer
self.positional_embedding = clip_model.positional_embedding
self.ln_final = clip_model.ln_final
self.text_projection = clip_model.text_projection
self.dtype = clip_model.dtype
def forward(self, prompts, tokenized_prompts):
x = prompts + self.positional_embedding.type(self.dtype)
x = x.permute(1, 0, 2) # NLD -> LND
x = self.transformer(x)
x = x.permute(1, 0, 2) # LND -> NLD
x = self.ln_final(x).type(self.dtype)
# 获取EOT(End of Text)标记的特征
x = x[torch.arange(x.shape[0]), tokenized_prompts.argmax(dim=-1)] @ self.text_projection
return x
# 构建Transformer模型类,用于图像和文本编码
class build_transformer(nn.Module):
def __init__(self, num_classes, camera_num, view_num, cfg):
super(build_transformer, self).__init__()
self.model_name = cfg.MODEL.NAME
self.cos_layer = cfg.MODEL.COS_LAYER
self.neck = cfg.MODEL.NECK
self.neck_feat = cfg.TEST.NECK_FEAT
if self.model_name == 'ViT-B-16':
self.in_planes = 768
self.in_planes_proj = 512
elif self.model_name == 'RN50':
self.in_planes = 2048
self.in_planes_proj = 1024
self.num_classes = num_classes
self.camera_num = camera_num
self.view_num = view_num
self.sie_coe = cfg.MODEL.SIE_COE
# 分类器和瓶颈层初始化
self.classifier = nn.Linear(self.in_planes, self.num_classes, bias=False)
self.classifier.apply(weights_init_classifier)
self.classifier_proj = nn.Linear(self.in_planes_proj, self.num_classes, bias=False)
self.classifier_proj.apply(weights_init_classifier)
self.bottleneck = nn.BatchNorm1d(self.in_planes)
self.bottleneck.bias.requires_grad_(False)
self.bottleneck.apply(weights_init_kaiming)
self.bottleneck_proj = nn.BatchNorm1d(self.in_planes_proj)
self.bottleneck_proj.bias.requires_grad_(False)
self.bottleneck_proj.apply(weights_init_kaiming)
# 计算输入图像的分辨率
self.h_resolution = int((cfg.INPUT.SIZE_TRAIN[0]-16)//cfg.MODEL.STRIDE_SIZE[0] + 1)
self.w_resolution = int((cfg.INPUT.SIZE_TRAIN[1]-16)//cfg.MODEL.STRIDE_SIZE[1] + 1)
self.vision_stride_size = cfg.MODEL.STRIDE_SIZE[0]
# 加载CLIP模型
clip_model = load_clip_to_cpu(self.model_name, self.h_resolution, self.w_resolution, self.vision_stride_size)
clip_model.to("cuda")
self.image_encoder = clip_model.visual
# 摄像机和视角嵌入初始化
if cfg.MODEL.SIE_CAMERA and cfg.MODEL.SIE_VIEW:
self.cv_embed = nn.Parameter(torch.zeros(camera_num * view_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('camera number is : {}'.format(camera_num))
elif cfg.MODEL.SIE_CAMERA:
self.cv_embed = nn.Parameter(torch.zeros(camera_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('camera number is : {}'.format(camera_num))
elif cfg.MODEL.SIE_VIEW:
self.cv_embed = nn.Parameter(torch.zeros(view_num, self.in_planes))
trunc_normal_(self.cv_embed, std=.02)
print('view number is : {}'.format(view_num))
dataset_name = cfg.DATASETS.NAMES
self.prompt_learner = PromptLearner(num_classes, dataset_name, clip_model.dtype, clip_model.token_embedding)
self.text_encoder = TextEncoder(clip_model)
def forward(self, x=None, label=None, get_image=False, get_text=False, cam_label=None, view_label=None):
# 获取文本特征
if get_text:
prompts = self.prompt_learner(label)
text_features = self.text_encoder(prompts, self.prompt_learner.tokenized_prompts)
return text_features
# 获取图像特征
if get_image:
image_features_last, image_features, image_features_proj = self.image_encoder(x)
if self.model_name == 'RN50':
return image_features_proj[0]
elif self.model_name == 'ViT-B-16':
return image_features_proj[:, 0]
# 获取图像特征并处理相机和视角嵌入
if self.model_name == 'RN50':
image_features_last, image_features, image_features_proj = self.image_encoder(x)
img_feature_last = nn.functional.avg_pool2d(image_features_last, image_features_last.shape[2:4]).view(x.shape[0], -1)
img_feature = nn.functional.avg_pool2d(image_features, image_features.shape[2:4]).view(x.shape[0], -1)
img_feature_proj = image_features_proj[0]
elif self.model_name == 'ViT-B-16':
if cam_label is not None and view_label is not None:
cv_embed = self.sie_coe * self.cv_embed[cam_label * self.view_num + view_label]
elif cam_label is not None:
cv_embed = self.sie_coe * self.cv_embed[cam_label]
elif view_label is not None:
cv_embed = self.sie_coe * self.cv_embed[view_label]
else:
cv_embed = None
image_features_last, image_features, image_features_proj = self.image_encoder(x, cv_embed)
img_feature_last = image_features_last[:, 0]
img_feature = image_features[:, 0]
img_feature_proj = image_features_proj[:, 0]
feat = self.bottleneck(img_feature)
feat_proj = self.bottleneck_proj(img_feature_proj)
if self.training:
cls_score = self.classifier(feat)
cls_score_proj = self.classifier_proj(feat_proj)
return [cls_score, cls_score_proj], [img_feature_last, img_feature, img_feature_proj], img_feature_proj
else:
if self.neck_feat == 'after':
# 测试时使用BN后的特征
return torch.cat([feat, feat_proj], dim=1)
else:
return torch.cat([img_feature, img_feature_proj], dim=1)
# 加载预训练模型参数
def load_param(self, trained_path):
param_dict = torch.load(trained_path)
for i in param_dict:
self.state_dict()[i.replace('module.', '')].copy_(param_dict[i])
print('Loading pretrained model from {}'.format(trained_path))
def load_param_finetune(self, model_path):
param_dict = torch.load(model_path)
for i in param_dict:
self.state_dict()[i].copy_(param_dict[i])
print('Loading pretrained model for finetuning from {}'.format(model_path))
# 创建模型的函数
def make_model(cfg, num_class, camera_num, view_num):
model = build_transformer(num_class, camera_num, view_num, cfg)
return model
from .clip import clip
# 加载CLIP模型到CPU
def load_clip_to_cpu(backbone_name, h_resolution, w_resolution, vision_stride_size):
url = clip._MODELS[backbone_name]
model_path = clip._download(url)
try:
# 尝试加载JIT模型
model
= torch.jit.load(model_path, map_location="cpu").eval()
state_dict = None
except RuntimeError:
state_dict = torch.load(model_path, map_location="cpu")
model = clip.build_model(state_dict or model.state_dict(), h_resolution, w_resolution, vision_stride_size)
return model
# 提示词学习器类,生成适合特定任务的提示词
class PromptLearner(nn.Module):
def __init__(self, num_class, dataset_name, dtype, token_embedding):
super().__init__()
if dataset_name == "VehicleID" or dataset_name == "veri":
ctx_init = "A photo of a X X X X vehicle."
else:
ctx_init = "A photo of a X X X X person."
ctx_dim = 512
# 初始化上下文向量
ctx_init = ctx_init.replace("_", " ")
n_ctx = 4
tokenized_prompts = clip.tokenize(ctx_init).cuda()
with torch.no_grad():
embedding = token_embedding(tokenized_prompts).type(dtype)
self.tokenized_prompts = tokenized_prompts # torch.Tensor
n_cls_ctx = 4
cls_vectors = torch.empty(num_class, n_cls_ctx, ctx_dim, dtype=dtype)
nn.init.normal_(cls_vectors, std=0.02)
self.cls_ctx = nn.Parameter(cls_vectors)
# 保存这些令牌向量
self.register_buffer("token_prefix", embedding[:, :n_ctx + 1, :])
self.register_buffer("token_suffix", embedding[:, n_ctx + 1 + n_cls_ctx:, :])
self.num_class = num_class
self.n_cls_ctx = n_cls_ctx
def forward(self, label):
cls_ctx = self.cls_ctx[label]
b = label.shape[0]
prefix = self.token_prefix.expand(b, -1, -1)
suffix = self.token_suffix.expand(b, -1, -1)
prompts = torch.cat(
[
prefix, # (n_cls, 1, dim)
cls_ctx, # (n_cls, n_ctx, dim)
suffix, # (n_cls, *, dim)
],
dim=1,
)
return prompts