读MetaGait代码

前置问题

关于分布式,可能是系统、网络等问题我最终还是取消掉了,下面这些尝试使用分布式时报的错姑且记录一下。。。
##############################

module ‘distutils’ has no attribute ‘version’

pip install setuptools==59.5.0

No module named ‘_distutils_hack’

安装setuptools时可能会提示这么一句,那就打开提示中的\lib\site-packages\distutils-precedence.pth,在“import os;”后面加个回车即可

缺包wandb einops

安就完了,奥利给

ValueError: Error initializing torch.distributed using env:// rendezvous: environment variable *** expected, but not set

之前跑gaitedge时也出过这种问题,好像是关于分布式的配置,当时用的小破笔记本所以这些关于多线程的全部取消掉了

os.environ['RANK'] = '0'
os.environ['WORLD_SIZE'] = '2'# =torch.cuda.device_count()
os.environ['MASTER_ADDR'] = 'localhost'
os.environ['MASTER_PORT'] = '35632'

RuntimeError: Distributed package doesn’t have NCCL built in

好像是因为win不支持NCCL
所以还是参考之前的做法替换一下

torch.distributed.init_process_group("gloo")

#############################

首先对比一下yaml配置

因为输入是pkl文件,所以和gaitgl对比
1.学习率
gaitgl是经典的1e-4,而MetaGait是3.5e-4
2.scheduler

#gaitgl
scheduler_cfg:
  gamma: 0.1
  milestones:
    - 70000
  scheduler: MultiStepLR
#metagait
scheduler_cfg:
  gamma: 0.1
  milestones:
    - 80000
    - 100000
  scheduler: warmup
  warmup_iters: 10000
  warmup_factor: 0.01

具体调用时

self.scheduler =  WarmupMultiStepLR(optimizer=self.optimizer, milestones=cfgs['scheduler_cfg']['milestones'], warmup_iters=cfgs['scheduler_cfg']['warmup_iters'], warmup_factor=cfgs['scheduler_cfg']['warmup_factor'])

MultiStepLR其实就是每逢70000这样一个milestones,当前学习率就要乘以gamma

而WarmupMultiStepLR通过 _get_warmup_factor_at_iter 函数计算预热因子

if iter >= warmup_iters: #10000
    return 1.0

if method == "constant":
    return warmup_factor #0.01
elif method == "linear": #没给,那就是默认的Linear
    alpha = iter / warmup_iters
    return warmup_factor * (1 - alpha) + alpha
else:
    raise ValueError("Unknown warmup method: {}".format(method))

warmup factor = warmup factor × ( 1 − i t e r warmup iters ) + i t e r warmup iters \text{warmup factor}=\text{warmup factor}×(1−\frac{iter}{\text{warmup iters}})+\frac{iter}{\text{warmup iters}} warmup factor=warmup factor×(1warmup itersiter)+warmup itersiter
然后根据阶段性的milestones和gamma计算实际学习率。

[base_lr * warmup_factor * self.gamma ** bisect_right(self.milestones, self.last_epoch)
            for base_lr in self.base_lrs]

lr multi step = base lr × warmup factor × g a m m a bisect right ( m i l e s t o n e s , last epoch ) \text{lr multi step}=\text{base lr}\times \text{warmup factor}\times gamma^{\text{bisect right}(milestones,\text{last epoch})} lr multi step=base lr×warmup factor×gammabisect right(milestones,last epoch)

bisect_right用于在有序序列中找到某个值应该插入的位置,同时保持序列的有序性。具体来说,bisect_right返回一个索引,该索引是在有序序列中查找插入某个元素后,保持有序状态的最右侧位置。

在给定有序序列 a 中,bisect_right(a, x) 将找到元素 x在序列中应该插入的位置,并返回一个索引,以确保插入后序列仍然保持有序。如果 x 已经存在于序列中,该函数将返回在最右侧出现的位置。

例如,考虑以下代码:

from bisect import bisect_right

a = [1, 3, 5, 7, 9] 
x = 6

index = bisect_right(a, x) 
print(f"Index to insert {x}: {index}")

在这个例子中,x 的值是 6,而有序序列 a 是 [1, 3, 5, 7, 9]。bisect_right(a, x) 将返回值 3,因为将元素 6 插入到位置 3 后,序列仍然保持有序。所以,这个函数在这里返回的索引是插入位置的索引。

在你提供的代码中,bisect_right 的使用是为了找到阶段性里程碑(milestones)列表中最接近 last_epoch 的值的位置,从而确定应该应用哪个学习率衰减因子。
——ChatGPT

3.学习轮次起码120000,gaitgl是经典的80000

开始debug

一、首先类似地通过dataset取数据
例如首先读取的是data_list(5, 64, 64)的uint8的pkl(这里5是对应的总帧数,例如下一个就是108了),seq_info['037', 'bg-02', '018', ['datasets/CASIA-B-pkl\\037\\bg-02\\018\\018.pkl']]
(因为突然有些懵逼这个只有5帧的id是怎么在后面整出来30帧的,所以再研究一下sampler)

训练
'sampler': {
'batch_shuffle': True, 
'batch_size': [8, 8], 
'frames_num_fixed': 30, 
'frames_num_max': 50, 
'frames_num_min': 25, 
'sample_type': 'fixed_ordered', 
'type': 'TripletSampler', 
'frames_skip_num': 0}
测试
{'batch_size': 1, 
'sample_type': 'all_ordered', 
'type': 'InferenceSampler'}

二、接着预处理inputs

# TripletSampler.__iter__
while True:
    sample_indices = []
    pid_list = sync_random_sample_list(
        self.dataset.label_set, self.batch_size[0])
    # 从训练的总74类id中取出8类id作为当前batch
    for pid in pid_list:
        indices = self.dataset.indices_dict[pid]
        indices = sync_random_sample_list(
            indices, k=self.batch_size[1])
        sample_indices += indices
    # 遍历每一个ID
    # 从id对应的样本索引(包括10种属性、11种角度的pkl,
    # 所以基本上都是110个pkl
    # 有的不够是因为他有些视角下原本没有帧样本,被跳过去了)
    # 中取出8个作为当前batch
    if self.batch_shuffle:
        sample_indices = sync_random_sample_list(
            sample_indices, len(sample_indices))

    total_batch_size = self.batch_size[0] * self.batch_size[1]
    total_size = int(math.ceil(total_batch_size /
                               self.world_size)) * self.world_size
    sample_indices += sample_indices[:(
        total_batch_size - len(sample_indices))]

    sample_indices = sample_indices[self.rank:total_size:self.world_size]
    yield sample_indices

# CollateFn.__call__
batch_size = len(batch)
# 此时的batch是bs个tuple组成的list(也就对应之前的bs=64个pkl)
# tuple第二项是id、属性、角度、[pkl路径]
# tuple第一项是(_, 64, 64)的numpy
# 其中_就表示原本这个视角下有多少帧的图像

feature_num = len(batch[0][0])
# 有注释说这里就是单纯的1,除非后续兼容了二值图或者骨架建模什么的

seqs_batch, labs_batch, typs_batch, vies_batch = [], [], [], []

for bt in batch:
    seqs_batch.append(bt[0])
    labs_batch.append(self.label_set.index(bt[1][0]))
    typs_batch.append(bt[1][1])
    vies_batch.append(bt[1][2])

global count
count = 0

def sample_frames(seqs):
    global count
    sampled_fras = [[] for i in range(feature_num)]
    seq_len = len(seqs[0])
    indices = list(range(seq_len))

    if self.sampler in ['fixed', 'unfixed']:
        if self.sampler == 'fixed':
            frames_num = self.frames_num_fixed
        else:
            frames_num = random.choice(
                list(range(self.frames_num_min, self.frames_num_max+1)))

        if self.ordered:
            fs_n = frames_num + self.frames_skip_num
            # 30
            if seq_len < fs_n:
            # 例如前面质疑过的只有5帧的这一个样本
                it = math.ceil(fs_n / seq_len)
                # 通过进一让扩充后的帧序列绰绰有余
                # 扩充其实就有点像重复寻址,不过是让索引拷贝了几份
                # 但是其实顺序取帧主要不就是要学习这个人有时序的动作步态
                # 这里重复的头接上一周期的尾
                # 这样的衔接不就会造成错误的步态时序吗
                # 不如直接当做噪声去除
                seq_len = seq_len * it
                indices = indices * it

            start = random.choice(list(range(0, seq_len - fs_n + 1)))
            # 随机选取一个起始点
            # 保证从这开始往后能够取到30帧
            end = start + fs_n
            idx_lst = list(range(seq_len))
            idx_lst = idx_lst[start:end]
            idx_lst = sorted(np.random.choice(
                idx_lst, frames_num, replace=False))
            # 为什么前面已经通过start:end]截取到了30帧,这里却还要随机选取?
            # 大概是因为前面其实还涉及到一个self.frames_skip_num
            # 使得选取帧之间还会有些跳帧
            # 不过不是均匀跳帧,只是多了一些备选帧罢了
            # 这里就相当于一个shuffle吧,然后再排回原来的顺序
            indices = [indices[i] for i in idx_lst]
        else:
            replace = seq_len < frames_num

            if seq_len == 0:
                get_msg_mgr().log_debug('Find no frames in the sequence %s-%s-%s.'
                                        % (str(labs_batch[count]), str(typs_batch[count]), str(vies_batch[count])))

            count += 1
            indices = np.random.choice(
                indices, frames_num, replace=replace)

    for i in range(feature_num):
        for j in indices[:self.frames_all_limit] if self.frames_all_limit > -1 and len(indices) > self.frames_all_limit else indices:
            sampled_fras[i].append(seqs[i][j])
    return sampled_fras
    # 存储成为[30个(60,60)的numpy]

# f: feature_num
# b: batch_size
# p: batch_size_per_gpu
# g: gpus_num
fras_batch = [sample_frames(seqs) for seqs in seqs_batch]  # [b, f]
# [64个[30个(60,60)的numpy]]
batch = [fras_batch, labs_batch, typs_batch, vies_batch, None]

if self.sampler == "fixed":
    fras_batch = [[np.asarray(fras_batch[i][j]) for i in range(batch_size)]
                  for j in range(feature_num)]  # [f, b]
# 相当于把第二维那个对应feature_num=1的维度转置到最外面
else:
    seqL_batch = [[len(fras_batch[i][0])
                   for i in range(batch_size)]]  # [1, p]

    def my_cat(k): return np.concatenate(
        [fras_batch[i][k] for i in range(batch_size)], 0)
    fras_batch = [[my_cat(k)] for k in range(feature_num)]  # [f, g]

    batch[-1] = np.asarray(seqL_batch)

batch[0] = fras_batch
return batch
# 然后再反压,就很奇怪的多余

取到64个(8类,每类8个),对应inputs[0]是64个(30, 64, 64)->seq;inputs[1]是64个id->lab;inputs[2]是64个属性类型->typ;inputs[3]是64个角度->vie;inputs[4]是None->seqL
三、输入网络
ipts-》[torch.Size([64, 30, 64, 44])]
labs
typs
vies
seqL
把ipts反压成sils-》torch.Size([64, 1, 30, 64, 44])

MetaGait(
  ####### gaitgl有的部分 #########
  (conv3d): Sequential(
    (0): BasicConv3d(
      (conv3d): Conv3d(1, 32, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
    (1): LeakyReLU(negative_slope=0.01, inplace=True)
  )
  (LTA): Sequential(
    (0): BasicConv3d(
      (conv3d): Conv3d(32, 32, kernel_size=(3, 1, 1), stride=(3, 1, 1), bias=False)
    )
    (1): LeakyReLU(negative_slope=0.01, inplace=True)
  )
  (GLConvA0): GLConv(
    (global_conv3d): BasicConv3d(
      (conv3d): Conv3d(32, 64, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
    (local_conv3d): BasicConv3d(
      (conv3d): Conv3d(32, 64, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
  )
  (MaxPool0): MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2), padding=0, dilation=1, ceil_mode=False)
  (GLConvA1): GLConv(
    (global_conv3d): BasicConv3d(
      (conv3d): Conv3d(64, 128, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
    (local_conv3d): BasicConv3d(
      (conv3d): Conv3d(64, 128, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
  )
  (GLConvB2): GLConv(
    (global_conv3d): BasicConv3d(
      (conv3d): Conv3d(128, 128, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
    (local_conv3d): BasicConv3d(
      (conv3d): Conv3d(128, 128, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1), bias=False)
    )
  )
  ###############################
  此时有outs-》torch.Size([64, 128, 10, 64, 22])
  (eca): LocalAttention(
    (avg_pool): AdaptiveAvgPool3d(output_size=(1, 1, 1))#自适应降维到给定尺寸
    得到weight-》torch.Size([64, 128, 1, 1, 1])
    
    (dynamic_conv): MHN(输入weight
      先mean(dim=[2,3,4], keepdim=True),但效果其实相当于没处理
      
      (reduce): Conv3d(128, 16, kernel_size=(1, 1, 1), stride=(1, 1, 1))
      得到x_gap-》torch.Size([64, 16, 1, 1, 1])
      
      (wn_fc1): Conv3d(16, 6, kernel_size=(1, 1, 1), stride=(1, 1, 1))
      得到x_w-》torch.Size([64, 6, 1, 1, 1]),做sigmoid
            
      (wn_fc2): Conv3d(6, 384, kernel_size=(1, 1, 1), stride=(1, 1, 1), groups=6, bias=False)
      得到x_w-》torch.Size([64, 384, 1, 1, 1])
      (conv3d_sample_by_sample):
      		如果weight的bs为1(也就是test):
      			先把x_w给view成(3,128,1,1,1)
      			out=F.conv3d(weight,weight=x_w,stride=(1,1,1),padding=(0,0,0),groups=1)#groups对应分组卷积
      		否则:
      			先把weight给view成(1,-1,weight.shape[2], weight.shape[3],weight.shape[4])也即torch.Size([1, 8192, 1, 1, 1])
      			把x_w给view成(bs*3,128,1,1,1)也即torch.Size([192, 128, 1, 1, 1])
      			out=F.conv3d(weight,weight=x_w,stride=(1,1,1),padding=(0,0,0),groups=1*bs)
      			得到weight-》torch.Size([1, 192, 1, 1, 1])再view成torch.Size([64,3, 1, 1, 1])
    )    
    
    (avg_pool): AdaptiveAvgPool3d(output_size=(1, 1, 1))#自适应降维到给定尺寸
    再对x做一次后经过压缩,再做einops.rearrange得到y-》torch.Size([64,1, 128])#这个爱因斯坦函数其实就相当于transpose、view或permute吧
    
    (conv1): Conv1d(1, 1, kernel_size=(1,), stride=(1,), bias=False)
    输入y得到y1-》torch.Size([64,1, 128])
    
    (conv3): Conv1d(1, 1, kernel_size=(3,), stride=(1,), padding=(1,), bias=False)
    输入y得到y3-》torch.Size([64,1, 128])
    
    (conv5): Conv1d(1, 1, kernel_size=(5,), stride=(1,), padding=(2,), bias=False)
    输入y得到y5-》torch.Size([64,1, 128])
    
    三个分别einops.rearrange再反压得到torch.Size([64, 128, 1, 1, 1])
    (sigmoid): Sigmoid()分别
    
    对weight的第1维正好分三份有weight1~weight3
    
    计算x * y1 * weight1 + x * y5 * weight2 + x * y3 * weight3返回outs1->torch.Size([64, 128, 10, 64, 22])
  )
  (se): SELayer(
    还是输入outs-》torch.Size([64, 128, 10, 64, 22])
    (avg_pool): AdaptiveAvgPool3d(output_size=(1, 1, 1))
    再压缩得到y->torch.Size([64, 128])
    
    (fc): Sequential(
      (0): Linear(in_features=128, out_features=128, bias=False)
      (1): LeakyReLU(negative_slope=0.01, inplace=True)
      (2): Linear(in_features=128, out_features=128, bias=False)
      (3): Sigmoid()
    )再反压得到torch.Size([64, 128, 1, 1, 1])

  )返回outs2=outs*y-》torch.Size([64, 128, 10, 64, 22])
  outs = outs1 + outs2
  
  (TP): PackSequenceWrapper()
  # 参数和gaitgl不太一样
  #MetaGait:
  #outs = self.TP(outs, dim=2, seq_dim=2,   seqL=seqL)[0] 
  #gaitgl:
  #outs = self.TP(outs, seqL=seqL, options={"dim": 2})[0]  
  # 不过看本质应该都是一样的最大池化,维度也都是2
  得到outs-》torch.Size([64, 128, 64, 22])

  (HPP): GeMHPP()
  得到outs-》torch.Size([64, 128, 64])
  随后又与gaitgl不同的是
  经过permute得到得到outs-》torch.Size([64, 64, 128])

  (Head0): SeparateFCs()
  其实就是单独乘一个权重矩阵
  得到gait-》torch.Size([64, 64, 128])
  
  再次与gaitgl不同的是
  gait被permute成torch.Size([64, 128, 64])后直接执行bn和head1
  (Bn): SyncBatchNorm(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  得到bnft后再permute成torch.Size([64, 64, 128])后直接给head1
  (Head1): SeparateFCs()
  得到logi-》torch.Size([64, 64, 74])
  
  最后写进retval的bnft被permute成torch.Size([64, 64, 128])
  #原本输入给head1的是2,0,1
  #这里是0,2,1
  logi也1,0,2permute成torch.Size([64, 64, 74])
  #也就是前两维的64调换顺序
)
training_feat:
  triplet:
    embeddings: torch.Size([64, 64, 128])
    labels: torch.Size([64])
  softmax:
    logits: torch.Size([64, 64, 74])
    labels: torch.Size([64])
visual_summary:
  image/sils: torch.Size([1920, 1, 64, 44])
inference_feat:
  embeddings: torch.Size([64, 64, 128])
输出结构和gaitgl一致

局部依赖

MHN

在这里插入图片描述
本质就是构造一个映射Fmeta使得通过当前输入得到 Fcali的权重Wcali
于是代码中有eca中的avg_pool对输入x得到一个m
在这里插入图片描述
随后两次卷积(wn_fc)得到Wcali(不过代码中m还经过了一次名为reduce的卷积。也没有经历两次leakyrelu,而是一次sigmoid)
在这里插入图片描述
最后再直接F.conv3d得到flocal

MTA

在这里插入图片描述
论文里是整体和局部一起的,代码里是分开的,其实就是 F c a l i ( X ) ⨂ X F_{cali}(X)\bigotimes X Fcali(X)X
这里局部的代码就是1、3、5三种尺度的门控卷积经过sigmoid得到对应的y,应该是对应公式里的G

局部依赖

在这里插入图片描述
也就对应代码SE中的avg_pool后接两层FC,这里倒是有一层leakyrelu,最后还有一层sigmoid应该是对应上面MTA总公式里那个。最后的x*y也满足了全局这一部分的 F c a l i ( X ) ⨂ X F_{cali}(X)\bigotimes X Fcali(X)X
在这里插入图片描述
(不过论文里说Wg这俩也都是MHN自适应参数化得到的?代码里就是简单的模式化Linear啊。而且最后整体也没有最后的门控权重G,直接就和局部的结果求和了)

MTP

最后的加权池化,代码里其实就是和gaitgl一致的,没看出有什么并行分支,还是串行的最大池化TP、GeM广义平均池化(甚至不愿意加一个GAP)
看issue里说这应该不是完整的代码,所以whatever吧

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值