openpose保姆级教程代码详细解析——训练部分

news2024/12/25 9:34:51

一:前言        

        OpenPose是一个基于深度学习的人体姿势估计库,它可以从图像或视频中准确地检测和估计人体的关键点和姿势信息。OpenPose的目标是将人体姿势估计变成一个实时、多人、准确的任务。它的原理部分已经在上一篇非常详细的讲解了——本节介绍训练部分

二:导入数据,定义参数

        因为是基于coco数据集来做的,所有导入的都是coco的api来处理数据,所以很多操作可以使用api进行简化

# DATA_DIR = 'D:\\eclipse-workspace\\PyTorch\\coco-data'
DATA_DIR = 'C:\\python\\code\\tangyudi\\yolo\\PyTorch-YOLOv3\\data\\coco'

ANNOTATIONS_TRAIN = [os.path.join(DATA_DIR, 'annotations', item) for item in ['person_keypoints_train2014.json']]
ANNOTATIONS_VAL = os.path.join(DATA_DIR, 'annotations', 'person_keypoints_val2014.json')
IMAGE_DIR_TRAIN = os.path.join(DATA_DIR, 'images\\train2014')
IMAGE_DIR_VAL = os.path.join(DATA_DIR, 'images\\val2014')

        在使用GPU进行深度学习训练或推断时,数据通常需要从主机内存传输到GPU内存,这涉及到内存之间的数据拷贝操作。这些数据拷贝操作可能会引入一定的开销,降低数据传输效率。

        通过将数据固定在主机内存中,可以避免每次传输数据时的拷贝操作,从而减少数据传输的开销。这对于大规模的数据传输、高频率的数据传输或需要实时性能的任务非常有用。

def cli():
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    # train_cli(parser)是cli()函数的一部分,它负责添加训练相关的命令行参数。
    train_cli(parser)
    parser.add_argument('-o', '--output', default=None,
                        help='output file')
    parser.add_argument('--stride-apply', default=1, type=int,
                        help='apply and reset gradients every n batches')
    parser.add_argument('--epochs', default=75, type=int,
                        help='number of epochs to train')
    parser.add_argument('--freeze-base', default=0, type=int,
                        help='number of epochs to train with frozen base')
    parser.add_argument('--pre-lr', type=float, default=1e-4,
                        help='pre learning rate')
    parser.add_argument('--update-batchnorm-runningstatistics',
                        default=False, action='store_true',
                        help='update batch norm running statistics')
    parser.add_argument('--square-edge', default=368, type=int,
                        help='square edge of input images')
    parser.add_argument('--ema', default=1e-3, type=float,
                        help='ema decay constant')
    parser.add_argument('--debug-without-plots', default=False, action='store_true',
                        help='enable debug but dont plot')
    parser.add_argument('--disable-cuda', action='store_true',
                        help='disable CUDA')
    parser.add_argument('--model_path', default='./network/weight/', type=str, metavar='DIR',
                        help='path to where the model saved')
    args = parser.parse_args()

    # add args.device
    args.device = torch.device('cpu')#设置设备
    args.pin_memory = False #内存固定
    if not args.disable_cuda and torch.cuda.is_available():
        args.device = torch.device('cuda')
        args.pin_memory = True  

    return args

三:初始化加载数据成类的属性,打包成dataloder

def train_factory(args, preprocess, target_transforms):
    train_datas = [datasets.CocoKeypoints(
        root=args.train_image_dir,
        annFile=item,
        preprocess=preprocess,
        image_transform=transforms.image_transform_train,
        target_transforms=target_transforms,
        n_images=args.n_images,
    ) for item in args.train_annotations]

    train_data = torch.utils.data.ConcatDataset(train_datas)
    # 初始化完后打包成dataloder
    train_loader = torch.utils.data.DataLoader(
        train_data, batch_size=args.batch_size, shuffle=True,
        pin_memory=args.pin_memory, num_workers=args.loader_workers, drop_last=True)

    val_data = datasets.CocoKeypoints(
        root=args.val_image_dir,
        annFile=args.val_annotations,
        preprocess=preprocess,
        image_transform=transforms.image_transform_train,
        target_transforms=target_transforms,
        n_images=args.n_images,
    )  # 初始化完后打包成dataloder
    val_loader = torch.utils.data.DataLoader(
        val_data, batch_size=args.batch_size, shuffle=False,
        pin_memory=args.pin_memory, num_workers=args.loader_workers, drop_last=True)

    return train_loader, val_loader, train_data, val_data

进入__getitem__函数

这是实际取数据的函数__getitem__,index是随机取的,而且每一行图片都要进行很多预处理操作
 def __getitem__(self, index):
        """
        Args:
            index (int): Index

        Returns:
            tuple: Tuple (image, target). target is the object returned by ``coco.loadAnns``.
        """
        # 获取图片的id,因为是基于coco数据集来做的,所有导入的都是coco的api来处理数据,所以很多操作可以使用api进行简化
        image_id = self.ids[index]
        # 根据id获取标注的信息
        ann_ids = self.coco.getAnnIds(imgIds=image_id, catIds=self.cat_ids)
        # 加载与之前步骤中获得的ann_ids对应的注释。它根据提供的ID从COCO数据集中获取注释信息。
        anns = self.coco.loadAnns(ann_ids)
        anns = copy.deepcopy(anns)

        # 直接利用api加载coco的图片,路径,图片名字,大小等等很多。 单单取[0]就这么多信息了。。。
        image_info = self.coco.loadImgs(image_id)[0]
        # 记录(通常是打印)图像信息
        self.log.debug(image_info)
        with open(os.path.join(self.root, image_info['file_name']), 'rb') as f:
            image = Image.open(f).convert('RGB')

        meta_init = {
            'dataset_index': index,
            'image_id': image_id,
            'file_name': image_info['file_name'],
        }

        # 元数据(Metadata)是描述数据的数据,它提供关于数据的特征、属性和其他相关信息的描述。元数据可以帮助理解和管理数据,包括数据的来源、
        # 格式、结构、内容、时间戳、创建者、修改历史、使用许可等。
        # 注释(anns)它是与图像关联的对象的标注信息。注释可以包括多个对象的标注,每个对象可能有不同的属性和标记。

        # self.preprocess方法用于对图像和注释进行预处理操作,可能包括缩放、裁剪、标准化等操作。并返回处理后的图像、注释和其他元数据。
        image, anns, meta = self.preprocess(image, anns, None)

        if isinstance(image, list):
            # 检查image是否为列表类型。如果image是列表,则表示有多张图像需要处理。
            return self.multi_image_processing(image, anns, meta, meta_init)
        # 进行单图像处理。,,meta_init表示元数据的初始值。
        # 只希望拿到anns的keypoints,坐标的含有是:(x,y,keypoints)keypoints(0,1,2)0是该点未被标记,1是标了但是被遮挡了,2是标了而且没被遮挡
        # 一般1和2有用到,0没用,因为没标注
        return self.single_image_processing(image, anns, meta, meta_init)

获得有效区域

    def single_image_processing(self, image, anns, meta, meta_init):
        # meta_init中的键值对更新到meta字典中。如果meta中已经存在相同的键,则会被meta_init中的值覆盖。
        meta.update(meta_init)

        # transform image
        original_size = image.size
        # 将图像进行变换操作,可能包括缩放、裁剪、标准化等操作,用于将图像转换为模型输入所需的格式。
        image = self.image_transform(image)
        # 确保图像变换过程中没有出现尺寸错误。
        assert image.size(2) == original_size[0]
        assert image.size(1) == original_size[1]

        # mask valid
        # valid_area可能表示一个矩形区域或是一个二值掩码(mask),用于指示图像中哪些像素属于有效区域。矩形区域可以由左上角和右下角坐标表示,
        # 或者可以使用二值掩码标记有效区域内的像素。meta['valid_area'] = [  0.  20. 368. 327.]也就是整个人的图像框

        # 从meta字典中获取名为valid_area的键对应的值。valid_area翻译过来就是:可能表示图像中的有效区域,也就是整个人的图像框。
        valid_area = meta['valid_area']#valid_area
        # 将图像中的非有效区域(根据valid_area进行定义)进行掩码处理,可能是将非有效区域的像素置为特定值或进行其他处理。
        # 相当于有效区域的置为1,无效置为0, 但是没有返回值?
        utils.mask_valid_area(image, valid_area)#传入图片和有效的区域

核心部分:生成热图和pafs


        最核心的部分:利用标注好的信息anns 来生成热图(heatmaps)和部件关联图(pafs)的真值信息。

vgg-19是3次下采样,假设输入的是368,那么采样后就是368/8=46

多了一个脖子外加一个背景

按照先后顺序来的,x和y来给你个方向,得到38个,[[1, 8], [8, 9], [9, 10], [1, 11], [11, 12], [12, 13], [1, 2], [2, 3], [3, 4], [2, 14], [1, 5], [5, 6], [6, 7], [5, 15], [1, 0], [0, 14], [0, 15], [14, 16], [15, 17]]
ef get_ground_truth(self, anns):
        #vgg-19是3次下采样,假设输入的是368,那么采样后就是368/8=46
        grid_y = int(self.input_y / self.stride)
        grid_x = int(self.input_x / self.stride)
        channels_heat = (self.HEATMAP_COUNT + 1)#19 HEATMAP_COUNT中coco给了17个,但是我们多了一个脖子所以18个,+1是背景所以是19
        channels_paf = 2 * len(self.LIMB_IDS)#按照先后顺序来的,x和y来给你个方向,得到38个,[[1, 8], [8, 9], [9, 10], [1, 11], [11, 12], [12, 13], [1, 2], [2, 3], [3, 4], [2, 14], [1, 5], [5, 6], [6, 7], [5, 15], [1, 0], [0, 14], [0, 15], [14, 16], [15, 17]]
        heatmaps = np.zeros((int(grid_y), int(grid_x), channels_heat))
        pafs = np.zeros((int(grid_y), int(grid_x), channels_paf))#对于躯干来说,没有躯干的位置,向量为0,有躯干的地方为1

        keypoints = []
        # anns有多少list,就说明有多少个人,anns里面的num_keypoints表示每个人体部位关键点有被标注的个数,有些可能没标有些可能标了但是被遮挡了(也就是1)
        # 第一个注释的"num_keypoints"为0,表示该注释中没有标记的关键点。第二个注释的"num_keypoints"为14,表示该注释中标记了14个关键点,等等等等
        for ann in anns:
            # 17种部位,3列(x,y,keypoint)
            single_keypoints = np.array(ann['keypoints']).reshape(17, 3)
            # 在这里添加个脖子,先找到左肩膀和右肩膀求平均,就是脖子的位置了
            single_keypoints = self.add_neck(single_keypoints)
            keypoints.append(single_keypoints)
        keypoints = np.array(keypoints)
        # 去除不合法的关节。具体而言,这个方法可能会处理一些关节坐标异常或无效的情况,例如超出图像范围、无效的坐标值等。
        keypoints = self.remove_illegal_joint(keypoints)

生成高斯热图

从0遍历到18,每个部位都遍历

# 有了标注点后就开始生成高斯热图,在ppt的第9页
        # confidance maps for body parts
        # 从0遍历到18,每个部位都遍历
        for i in range(self.HEATMAP_COUNT):
            # print(keypoints)
            joints = [jo[i] for jo in keypoints]  # 每次遍历所有人的同一种关键点,多少人就多少个
            # print(joints)
            # 每次遍历每个人的同一种部位的关键点生成热图(例如肩膀),然后是0的话就是没有标注的,所以判断是否大于0.5
            for joint in joints:
                # 1是标注被遮挡 2是标注且没被遮挡
                if joint[2] > 0.5:
                    center = joint[:2]  # 点坐标
                    # 每次gaussian_map都要赋值,因为要在这基础上生成高斯热图。需要一直累加生成高斯图
                    gaussian_map = heatmaps[:, :, i]
                    # 对这个点坐标center 生成高斯热图,#7.0是sigma
                    heatmaps[:, :, i] = putGaussianMaps(
                        center, gaussian_map,
                        7.0, grid_y, grid_x, self.stride)

生成pafs

       开始一个人(同一个人)中所有部位的,一个人一个人来遍历pafs向量 self.LIMB_IDS是规定的连接规则:在第十四行开始[[1, 8], [8, 9], [9, 10], [1, 11], [11, 12], [12, 13], # [1, 2], [2, 3], [3, 4], [2, 14], [1, 5], [5, 6], [6, 7], [5, 15], [1, 0], [0, 14], [0, 15], [14, 16], [15, 17]]

putVecMaps函数: 必须有标注的,否则没法算向量


        for i, (k1, k2) in enumerate(self.LIMB_IDS):
            # limb
            # 表示该位置是否被计算了多次(计算的数量)
            count = np.zeros((int(grid_y), int(grid_x)), dtype=np.uint32)
            for joint in keypoints:
                # 必须有标注的,否则没法算向量
                if joint[k1, 2] > 0.5 and joint[k2, 2] > 0.5:
                    #k1,k2是self.LIMB_IDS是规定的连接规则
                    centerA = joint[k1, :2]# [283. 280.]
                    centerB = joint[k2, :2]# [292.34824 294.96875]
                    # 拿到向量的结果。每次都取出两个通道?  每一个躯干位置,选择x和y两个方向0-2,2-4,4-6
                    vec_map = pafs[:, :, 2 * i:2 * (i + 1)]
                    # 使用putVecMaps 来真正的构建向量
                    pafs[:, :, 2 * i:2 * (i + 1)], count = putVecMaps(
                        centerA=centerA,
                        centerB=centerB,
                        accumulate_vec_map=vec_map,
                        count=count, grid_y=grid_y, grid_x=grid_x, stride=self.stride
                    )

        # background
        heatmaps[:, :, -1] = np.maximum(
            1 - np.max(heatmaps[:, :, :self.HEATMAP_COUNT], axis=2),
            0.
        )
        return heatmaps, pafs

使用putVecMaps 来真正的构建向量

计算向量

b-a算出来向量a到b的向量,它表示从 centerA 指向 centerB 的向量

范数是为了获取向量的长度或大小。范数可以衡量向量的大小或大小关系,它代表了向量从原点到达该点的距离。

计算向量叉乘根据阈值选择赋值区域,任何向量与单位向量的叉乘即为四边形的面积,

不管在不在躯干上都要计算一下平行四边形的所有点到a点的距离

在大胳膊区域里面的 被算过的点 要+1,该区域计算次数都+1,为了下面取平均

现在不是躯干上的置为1。没有被计算过的地方还是本身,任何书除以1还是等于自身

def putVecMaps(centerA, centerB, accumulate_vec_map, count, grid_y, grid_x, stride):
    centerA = centerA.astype(float)
    centerB = centerB.astype(float)
    # limb width # 做一个假设:假设都是正常人标准的人,特别高特别胖长得奇奇怪怪的先不考虑
    thre = 1
    centerB = centerB / stride # 映射到特征图中
    centerA = centerA / stride

    # b-a算出来向量a到b的向量,它表示从 centerA 指向 centerB 的向量。p19那一节,大胳膊向量,
    limb_vec = centerB - centerA
    # 范数是为了获取向量的长度或大小。范数可以衡量向量的大小或大小关系,它代表了向量从原点到达该点的距离。
    # 求范数 这里求出来的是向量长度,为了进行归一化
    norm = np.linalg.norm(limb_vec)
    if (norm == 0.0):
        # print 'limb is too short, ignore it...'
        return accumulate_vec_map, count

    # 那既然limb_vec = centerB - centerA能计算出来大小方向了,为什么还需要转换为单位向量????????
    # 这是因为单位向量具有相同的方向,但长度为1,可以用来表示方向信息而不考虑其具体长度。在某些情况下,只关注向量的方向而不关注其具体的长度是有意义的,比如计算角度、方向差异等。
    # limb_vec_unit计算得出单位向量
    limb_vec_unit = limb_vec / norm
    # print 'limb unit vector: {}'.format(limb_vec_unit)



    #寻找满足我这个大胳膊区域里面的所有点
    # To make sure not beyond the border of this two points
    min_x = max(int(round(min(centerA[0], centerB[0]) - thre)), 0)# 得到所有可能区域
    max_x = min(int(round(max(centerA[0], centerB[0]) + thre)), grid_x)
    min_y = max(int(round(min(centerA[1], centerB[1]) - thre)), 0)
    max_y = min(int(round(max(centerA[1], centerB[1]) + thre)), grid_y)
    # 找到大胳膊区域的矩形的上下左右四个点后,可以生成的只包围大胳膊的矩形框(的x范围和y的范围)
    range_x = list(range(int(min_x), int(max_x), 1))
    range_y = list(range(int(min_y), int(max_y), 1))
    # 生成一个网格(grid)来表示一个矩形区域,它们包含了矩形区域内所有的 x 坐标和 y 坐标。
    xx, yy = np.meshgrid(range_x, range_y)
    # 因为上面生成了gird,xx和yy是所有矩形的坐标,现在计算矩形中的所有点到a的距离(x和y的距离都算),# 不管在不在躯干上都要计算一下平行四边形的所有点到a点的距离
    ba_x = xx - centerA[0]
    ba_y = yy - centerA[1]
    # 计算完所有点的ba_x和ba_y后。计算向量叉乘根据阈值选择赋值区域,任何向量与单位向量的叉乘即为四边形的面积,
    # 画出图来就会明白,在胳膊上的点与a形成的四边形面积肯定小于在胳膊外面与a形成的面积要小
    limb_width = np.abs(ba_x * limb_vec_unit[1] - ba_y * limb_vec_unit[0])
    # mask is 2D # 小于阈值的表示在该区域上
    mask = limb_width < thre
    # 创建一个空的数组,以便在接下来的代码中进行填充和操作,都是对mask进行操作
    vec_map = np.copy(accumulate_vec_map) * 0.0

    # 之前计算得到的mask(表示大胳膊区域)用于存储大胳膊的(ture还是faslse)。
    # 通过索引 yy 和 xx,将 mask 中为真的区域对应的位置的值设置为 1,其他位置的值保持为 0。这样就得到了一个与大胳膊区域对应的二维数数组
    vec_map[yy, xx] = np.repeat(mask[:, :, np.newaxis], 2, axis=2)
    #  0*limb_vec_unit表示不是大胳膊区域的,1*limb_vec_unit是区域里的,而且还是是单位向量limb_vec_unit
    vec_map[yy, xx] *= limb_vec_unit[np.newaxis, np.newaxis, :]

    #筛选:最后得到46*46的矩阵,大于0的为True 都是躯干(也就是单位向量),小于0的是false,本身mask不是的,
    # 终于懂了为什么现在是还原到特征图去了,因为上面只是大胳膊的矩阵,现在要对特征图操作了,所以还原到特征图去
    mask = np.logical_or.reduce((np.abs(vec_map[:, :, 0]) > 0, np.abs(vec_map[:, :, 1]) > 0))

    #先算类加值?一会再平均?先算出重叠的总值
    # 对accumulate_vec_map数组与count数组进行逐元素的乘法运算。
    # count 使用了 count[:, :, np.newaxis],它在最后添加了一个新的维度,形状变为 (height, width, 1)。
    accumulate_vec_map = np.multiply(accumulate_vec_map, count[:, :, np.newaxis])
    # 加上当前关键点位置形成的向量
    accumulate_vec_map = accumulate_vec_map + vec_map
    # 在大胳膊区域里面的 被算过的点 要+1,该区域计算次数都+1,为了下面取平均
    count[mask == True] = count[mask == True] + 1

    #  下面两步操作是:先拿出来不在躯干上的,然后当前mask为true的(为false的)赋值为1为了当除数,
    # 没有被计算过的赋值给mask,现在的mask的ture是没有被计算过的
    mask = count == 0
    # 现在不是躯干上的置为1。没有被计算过的地方还是本身,任何书除以1还是等于自身
    count[mask == True] = 1

    # 算平均向量,令没有被计算过的地方还是本身(如果不赋值为1的话),   另外其他算过的就计算平均值
    accumulate_vec_map = np.divide(accumulate_vec_map, count[:, :, np.newaxis])
    # 现在不是躯干上的置为1,还原回去0
    count[mask == True] = 0
    #返回更新后的 累积向量场 和 计数。
    return accumulate_vec_map, count

四:model初始化:加载模型 和预训练模型

这样的设计可能是为了先让模型在新任务上进行一定的训练,以便自适应新任务的数据。然后在后续的训练中,通过释放冻结的权重参数,
整个模型可以进行更全面的调整和优化,以更好地适应新任务的数据。
    class rtpose_model(nn.Module):
        def __init__(self, model_dict):
            super(rtpose_model, self).__init__()
            self.model0 = model_dict['block0']
            self.model1_1 = model_dict['block1_1']
            self.model2_1 = model_dict['block2_1']
            self.model3_1 = model_dict['block3_1']
            self.model4_1 = model_dict['block4_1']
            self.model5_1 = model_dict['block5_1']
            self.model6_1 = model_dict['block6_1']

            self.model1_2 = model_dict['block1_2']
            self.model2_2 = model_dict['block2_2']
            self.model3_2 = model_dict['block3_2']
            self.model4_2 = model_dict['block4_2']
            self.model5_2 = model_dict['block5_2']
            self.model6_2 = model_dict['block6_2']
        model = rtpose_model(models)
        return model
# model初始化:加载模型
model = get_model(trunk='vgg19')  # 可以换网络, elif trunk == 'mobilenet':
model = torch.nn.DataParallel(model).cuda()
# load pretrained
use_vgg(model)  # 加载预训练模型。url = 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
# Fix the VGG weights first, and then the weights will be released
# 其中模型的某些层或参数不需要进行更新,以保持其先前训练好的权重或特征提取能力。
for i in range(20):  # 前20个子模块不更新 ,VGG19模型包含了总共19个层,VGG19模型的前20个层(索引0到19)在第一个循环中不会被更新。
    for param in model.module.model0[i].parameters():
        param.requires_grad = False
这个操作的目的是为了获取模型中需要计算梯度和进行优化的参数集合。在训练过程中,只有这些参数会被更新,
通过这个列表,你可以将它们传递给优化器,以便在训练过程中仅更新这些参数的值。
# 这个操作的目的是为了获取模型中需要计算梯度和进行优化的参数集合。在训练过程中,只有这些参数会被更新,
trainable_vars = [param for param in model.parameters() if param.requires_grad]
# 通过这个列表,你可以将它们传递给优化器,以便在训练过程中仅更新这些参数的值。
optimizer = torch.optim.SGD(trainable_vars, lr=args.lr,
                            momentum=args.momentum,
                            weight_decay=args.weight_decay,
                            nesterov=args.nesterov)

五:训练开始(包括训练和验证)

for epoch in range(5):
    # train for one epoch 进入def __getitem__(self, index)函数
    train_loss = train(train_loader, model, optimizer, epoch)

    # evaluate on validation set
    val_loss = validate(val_loader, model, epoch)

train

        根据给定的循环范围生成一系列带有特定格式的名称,并将它们保存在列表中供后续使用。在这个特定的例子中,生成的名称类似于 'loss_stage1_L1''loss_stage1_L2''loss_stage2_L1' 等。

def build_names():
    names = []

    for j in range(1, 7):
        for k in range(1, 3):
            names.append('loss_stage%d_L%d' % (j, k))
    return names

AverageMeter 对象和 meter_dict 字典,训练函数可以在训练过程中实时跟踪和记录各种指标的平均值和当前值,以便后续分析和监控训练进展。

def train(train_loader, model, optimizer, epoch):
    batch_time = AverageMeter()  # 跟踪批处理时间的平均值和当前值
    data_time = AverageMeter()
    losses = AverageMeter()

    meter_dict = {}
    for name in build_names():
        meter_dict[name] = AverageMeter()
    meter_dict['max_ht'] = AverageMeter()  # 'max_ht':用于跟踪最大关节点(身体关键点)的置信度值的指标。
    meter_dict['min_ht'] = AverageMeter()  # 'min_ht':用于跟踪最小关节点的置信度值的指标。
    meter_dict['max_paf'] = AverageMeter()  # 'max_paf':用于跟踪最大关节点之间部分关联场(Part Affinity Field)的置信度值的指标。
    meter_dict['min_paf'] = AverageMeter()  # min_paf':用于跟踪最小关节点之间部分关联场的置信度值的指标。

损失

saved_for_loss是append后的,他有heatmap_target和paf_target这两个预测值

然后与heatmap_target, paf_target这两个真实值做损失,这个是标注得到数据

total_loss, saved_for_log = get_loss(saved_for_loss, heatmap_target, paf_target)

完整训练过程

他有heatmap_target和paf_target这两个预测值,然后与heatmap_target, paf_target这两个真实值做损失,这个真实值在加载数据的时候生成的

def train(train_loader, model, optimizer, epoch):
    batch_time = AverageMeter()  # 跟踪批处理时间的平均值和当前值
    data_time = AverageMeter()
    losses = AverageMeter()

    meter_dict = {}
    for name in build_names():
        meter_dict[name] = AverageMeter()
    meter_dict['max_ht'] = AverageMeter()  # 'max_ht':用于跟踪最大关节点(身体关键点)的置信度值的指标。
    meter_dict['min_ht'] = AverageMeter()  # 'min_ht':用于跟踪最小关节点的置信度值的指标。
    meter_dict['max_paf'] = AverageMeter()  # 'max_paf':用于跟踪最大关节点之间部分关联场(Part Affinity Field)的置信度值的指标。
    meter_dict['min_paf'] = AverageMeter()  # min_paf':用于跟踪最小关节点之间部分关联场的置信度值的指标。

    # switch to train mode
    model.train()

    end = time.time()
    for i, (img, heatmap_target, paf_target) in enumerate(train_loader):
        # measure data loading time
        # writer.add_text('Text', 'text logged at step:' + str(i), i)

        # for name, param in model.named_parameters():
        #    writer.add_histogram(name, param.clone().cpu().data.numpy(),i)        
        data_time.update(time.time() - end)

        img = img.cuda()
        heatmap_target = heatmap_target.cuda()
        paf_target = paf_target.cuda()
        # compute output,获得喂给模型后的数据。任何计算损失
        _, saved_for_loss = model(img)
        # saved_for_loss是append后的,他有heatmap_target和paf_target这两个预测值,然后与heatmap_target, paf_target这两个真实值做损失
        total_loss, saved_for_log = get_loss(saved_for_loss, heatmap_target, paf_target)

        # 这段代码的主要目的是在迭代过程中更新和跟踪多个指标的值,包括损失函数和其他评估指标。这有助于监控模型的训练进展并进行性能评估。
        for name, hyt in meter_dict.items():
            """
            meter_dict.items():
            {'loss_stage1_L1': < __main__.AverageMeterobjectat0x0000023397D1DF10 >, 
                'loss_stage1_L2': < __main__.AverageMeterobjectat0x000002338368CDC0 >, 
                'loss_stage2_L1': < __main__.AverageMeterobjectat0x000002338368CF10 >, 
                'loss_stage2_L2': < __main__.AverageMeterobjectat0x000002338368CF70 >,
                'loss_stage3_L1': < __main__.AverageMeterobjectat0x000002338368CF40 >,
                'loss_stage3_L2': < __main__.AverageMeterobjectat0x000002338368CE50 >, 
                'loss_stage4_L1': < __main__.AverageMeterobjectat0x000002338368CEB0 >, 
                'loss_stage4_L2': < __main__.AverageMeterobjectat0x000002338368CDF0 >,
                'loss_stage5_L1': < __main__.AverageMetobjectat0x000002338368CD90 >, 
                'loss_stage5_L2': < __main__.AverageMeterobjectat0x000002338368CBB0 >, 
                'loss_stage6_L1': < __main__.AverageMeterobjectat0x000002338368CAC0 >, 
                'loss_stage6_L2': < __main__.AverageMeterobjectat0x000002338368CB50 >, 
                'max_ht': < __main__.AverageMeterobjectat0x0000023397D1DD00 >, 
                'min_ht': < __main__.AverageMeterobjectat0x0000023397D1DD90 >, 
                'max_paf': < __main__.AverageMeterobjectat0x000002338368CA00 >, 
                'min_paf': < __main__.AverageMeterobjectat0x000002338368CA60 >
            """
            print('name是什么?', name)
            print('hyt是什么?', hyt)
            meter_dict[name].update(saved_for_log[name], img.size(0))  # 在每个步骤中更新指标的值,并在需要时计算平均值。这有助于跟踪指标的变化和趋势。
        losses.update(total_loss, img.size(0))#img.size(0) 是当前批处理的图像数量

        # compute gradient and do SGD step
        optimizer.zero_grad()
        total_loss.backward()
        optimizer.step()

        # measure elapsed time
        batch_time.update(time.time() - end)
        end = time.time()
        if i % args.print_freq == 0:  # 每20轮打印1次输出
            print_string = 'Epoch: [{0}][{1}/{2}]\t'.format(epoch, i, len(train_loader))
            print_string += 'Data time {data_time.val:.3f} ({data_time.avg:.3f})\t'.format(data_time=data_time)
            print_string += 'Loss {loss.val:.4f} ({loss.avg:.4f})'.format(loss=losses)

            for name, value in meter_dict.items():
                print_string += '{name}: {loss.val:.4f} ({loss.avg:.4f})\t'.format(name=name, loss=value)
            print(print_string)
    return losses.avg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/649220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dynamic .NET TWAIN 8.3.3 for NetCore Crack

用于 WinForms 和 WPF 应用程序的扫描仪和网络摄像头捕获 SDK 适用于 .NET 应用程序的简单高效的扫描和网络摄像头 SDK Dynamsoft 的 Dynamic .NET TWAIN 是一个基于 TWAIN 和 DirectShow 标准的 .NET 文档成像 SDK。它为扫描仪和网络摄像头软件开发提供了丰富、高效且…

修改依赖包下的子依赖版本,前端项目安全扫描出来的漏洞——解决过程

为什么要升级&#xff0c;如图云桌面&#xff08;相当于堡垒机-远程桌面&#xff09;的项目审查是大概基于node16版本进行扫描的&#xff0c;本来我方是通过降版本从14到12绕过大范围更新&#xff0c;但现在躲得过初一躲不过十五&#xff0c;如何更新 package-lock.json 中的一…

DAY24:二叉树(十四)二叉搜索树中的插入操作+删除二叉搜索树中的节点(二叉树结构修改难点)

文章目录 701.二叉搜索树中的插入操作思路递归法如何保证连接的节点就是空节点的父节点&#xff1f; 迭代法迭代法注意debug测试 450.删除二叉搜索树中的节点&#xff08;坑较多&#xff0c;注意复盘&#xff09;思路最开始的写法debug测试1.使用了释放后的空间ERROR: AddressS…

通知!2023年湖北住建厅七大员新考和继续教育要求有变化了?启程别

通知&#xff01;2023年湖北住建厅七大员新考和继续教育要求有变化了&#xff1f;启程别 湖北住建厅七大员新考以及继续教育的相关要求都即将有一些变化了目前在征集意见的阶段&#xff0c;具体实施等后续具体通知 对于新考的变化主要是&#xff1a; 1.由原先报名之后只需要完成…

Vue中如何进行颜色选择与调色板

Vue中如何进行颜色选择与调色板 颜色选择和调色板是Web开发中常用的功能&#xff0c;它们可以帮助用户选择或调整颜色。Vue作为一个流行的JavaScript框架&#xff0c;提供了一些工具和库&#xff0c;可以方便地实现颜色选择和调色板功能。本文将介绍如何在Vue中进行颜色选择和…

【aspose-words】Aspose.Words for Java模板语法详细剖析

文章目录 前言&#x1f34a;缘由aspose-words模板语法再了解 &#x1f3af;主要目标实现3大重点 &#x1f381;快速链接&#x1f348;猜你想问如何与狗哥联系进行探讨1.关注公众号【JavaDog程序狗】2.踩踩狗哥博客 &#x1f36f;猜你喜欢文章推荐 正文&#x1f34b;aspose-word…

mfc140.dll丢失的解决方法,解析mfc140.dll这个文件

其实大部分人在使用计算机过程中&#xff0c;您可能会遇到mfc140.dll丢失的错误提示。这个错误会导致一些应用程序无法正常运行&#xff0c;那么要如何解决这个问题呢&#xff1f;今天小编就来给大家详细的解析mfc140.dll这个文件以及教大家 mfc140.dll丢失的解决方法。 目录 …

【算法与数据结构】349、LeetCode两个数组的交集

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;使用了一个哈希数组记录nums1出现的数字&#xff0c;然后遍历nums2&#xff0c;找到hash数组为1的值就…

Vue-Element-Admin项目学习笔记(6)Vuex状态管理

前情回顾&#xff1a; vue-element-admin项目学习笔记&#xff08;1&#xff09;安装、配置、启动项目 vue-element-admin项目学习笔记&#xff08;2&#xff09;main.js 文件分析 vue-element-admin项目学习笔记&#xff08;3&#xff09;路由分析一:静态路由 vue-element-adm…

yoloV5目标识别以及跟踪,功能识别动物(大象,犀牛,水牛,斑马)

yolo目标识别以及跟踪还是很强的嘞&#xff01; 一. YOLO V5我来啦 1. 前期准备 yolo V5项目下载 项目的github地址项目的gitee地址 使用git 克隆下来到项目目录下面就好 环境配置 在yolov5的文件下面有一个 requirements.txt文件,这里就是环境依赖的说明。 这里我以 vs…

DP学习之解码方法

DP学习第二篇之解码方法 91. 解码方法 - 力扣&#xff08;LeetCode&#xff09; 一. 题目解析 二. 题解 算法原理及代码 状态表示 tips: 经验题目要求。以i位置为结尾&#xff0c;。。。 dp[i]: 以i位置为结尾时&#xff0c;解码方法的总数 状态转移方程 tips: 用之前或…

5.3.2 因特网的路由协议(二)基于距离向量算法的RIP协议

5.3.2 因特网的路由协议&#xff08;二&#xff09;基于距离向量算法的RIP协议 一、RIP协议概念 RIP是Routing Information Protocol缩写&#xff0c;又称为路由信息协议&#xff0c;是最先得到应用的内部网关协议&#xff0c;RIP作为一个常在小型互联网中使用的路由信息协议…

【mmcls】mmdet中使用mmcls的网络及预训练模型

mmcls现在叫mmpretrain&#xff0c;以前叫mmclassification&#xff0c;这里为了统一称为mmcls。在基于MM框架的下游任务&#xff0c;例如检测(mmdetection)中可以使用mmcls中的backbone进行特征提取&#xff0c;但这就需要知道网络的参数以及输出特征的维度。本文简单介绍了在…

CDD诊断数据库的简单介绍

1. 什么是数据库? 数据库是以结构化方式组织的一个数据集合。 比如DBC数据库: Network nodes Display Rx Messages EngineState(0x123) 通过结构化的方式把网络节点Display里Rx报文EngineState(0x123)层层展开。这种方 式的好处是:层次清晰,结构分明,易于查找。 2. 什么…

ERROR: AddressSanitizer: heap-use-after-free on address

内存错误"heap-use-after-free"&#xff0c;这是因为在C中&#xff0c;当使用delete关键字释放对象的内存后&#xff0c;该对象仍然会保留指向已经被释放内存的指针。这个指针称为悬挂指针&#xff08;Dangling Pointer&#xff09;。如果我们试图访问已经被释放的内…

【Linux】15. 文件系统与软硬链接

1. 文件系统的引出 在之前的学习过程当中&#xff0c;我们知道当文件被打开后需要加载进内存&#xff0c;第一步为其创建struct file结构体描述其结构(操作系统需要管理被打开的文件&#xff1a;先描述再组织)&#xff0c;在通过进程当中的文件描述符指针指向文件描述符表&…

《机器学习算法竞赛实战》-chapter2问题建模

《机器学习算法竞赛实战》学习笔记&#xff0c;记录一下自己的学习过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 问题建模 当参赛者拿到竞赛题目时&#xff0c;首先应该考虑的事情就是问题建模&#xff0c;同时完成基线(baseline)模型的pipeline搭建&#xff0c;从…

芯片工程师平均薪酬排第一,入行就学这几个热门专业>>>

高考已经结束&#xff0c;对于广大考生来说&#xff0c;考一个理想的分数固然重要&#xff0c;但高考志愿的填报同样事关重大&#xff0c;它决定未来几年考生的学习走向&#xff0c;也会影响到考生未来职业生涯的长远发展。目前&#xff0c;北京、江苏、河南、湖南、海南、甘肃…

Spring Boot进阶(45): Spring Boot 如何返回统一结果包装?一文教会你 | 超级详细,建议收藏

1. 前言&#x1f525; 现如今是前后端分离的时代&#xff0c;如果没有统一的返回格式&#xff0c;给前端的结果各式各样&#xff0c;估计前端小伙伴就要骂街了。我们想对自定义异常抛出指定的状态码排查错误&#xff0c;对系统的不可预知的异常抛出友好一点的异常信息。我们想让…

Java基础重点概要(部分)

为工信部第六届全国计算机信息大赛准备 &#xff0c;主要复习以下内容。 Java基础及环境&#xff1a;JDK发展历史&#xff0c;不同版本的进阶内容。Java程序的编写、编译、调试。 Java程序设计基础&#xff1a;常量和变量的概念&#xff0c;声明方式和作用域。基本数据类型的定…