zoukankan      html  css  js  c++  java
  • 转pytorch中训练深度神经网络模型的关键知识点

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/weixin_42279044/article/details/101053719
                </div>
                                                    <!--一个博主专栏付费入口-->
             
             <!--一个博主专栏付费入口结束-->
            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
                                        <div id="content_views" class="markdown_views prism-github-gist">
                    <!-- flowchart 箭头图标 勿删 -->
                    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                        <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                    </svg>
                                            <h3><a name="t0"></a><a id="_0"></a>关于数据格式</h3>
    
    1. 默认日常描述图片尺寸,采用[w,h]的形式,比如一张图片是1280*800就是指宽w=1280, 高h=800。
      因此在cfg中所指定img scale = [1333, 800]就是指w=1333, h=800
      从而转入计算机后,要从w,h变成h,w
    2. 默认的大部分数据集,输出格式都是n,h,w,c和bgr格式,一方面是hwc更普遍,另一方面是opencv读取的就是bgr。
    3. pytorch中指定的数据格式是chw和rgb(非常重要!记住!),所以常规处理方法是:数据集输出都统一定义成hwc和bgr,再通过
      transform来转换成chw和rgb

    关于img/label与模型weight之间的数据格式匹配

    1. 输入img要修改为float()格式float32,否则跟weight不匹配报错
      输入label要修改为long()格式int64,否则跟交叉熵公式不匹配报错
      img = img.float()
      label = label.long()
      这两句要放在每个batch开始位置

      为了避免遗忘,可以把这部分操作集成到自定义的to_tensor()函数中,在每次开始转tensor的时候自动转换:
      if isinstance(data, torch.Tensor):
      return data
      elif isinstance(data, np.ndarray):
      return torch.from_numpy(data)
      elif isinstance(data, Sequence) and not mmcv.is_str(data):
      return torch.tensor(data)
      elif isinstance(data, int):
      return torch.LongTensor([data])
      elif isinstance(data, float):
      return torch.FloatTensor([data])

    2. 如果要把在GPU中运算的数据可视化,必须先变换到cpu,然后解除grad,最后转numpy才能使用。
      即:x1 = x.cpu().detach().numpy()

    关于图片标签值的定义在分类问题和检测问题上的区别

    1. 在纯分类任务中,数据集的label一般定义成从0开始,比如10类就是[0,9],这样的好处是在转独热编码的时候比较容易,
      比如标签2的独热编码就是[0,0,1,0], 标签0的独热编码就是[1,0,0,0]
    2. 而在物体检测任务中的分类子任务中,一般会把数据集的label定义成从1开始,比如10类就是[1,10], 这样做的目的是
      因为在检测任务中需要对anchor的身份进行指定,而比较简洁的处理是把负样本的anchor设定为label=0。所以相当于把
      label=0预留给anchor的负样本。

    关于transform中涉及的类型变换导致的错误

    1. transform和transform_inv中涉及的数据类型变换很多种类,很容易漏掉没有做而导致输出形式不对。

    2. 对于transform_inv的变换,需要重点关注

      • 逆变换需要把数据先从GPU取出来到cpu中并转换成numpy形式,才能进行后续逆变换和显示。
      • 默认数据集输出类型:hwc, bgr。采用这种默认输出形式,主要是因为用opencv作为底层函数的输出就是这种形式。
        而pytorch需要的形式是chw, rgb,所以经过transform后输出就是chw,rgb
      • 逆变换需要先变换chw为hwc,然后才变换rgb为bgr:因为rgb2bgr是基于最后一个维度是c来写的。
      • 逆变换需要把rgb转换为bgr
      • 逆变换需要把归一化和标准化的图片恢复成原始图片的数据形式,并且需要从float转换为unit8,这样才能被opencv识别。
      • 最后进行截取(0-255)范围内的数值,因为少部分数值在变换过程中会超过这个范围。

    关于训练过程中图片尺寸如何变换的问题?

    1. 通常会定义一个边框尺寸,比如scale = (300, 300),这是图片的最大尺寸范围。

    2. 图片首先经过transform,按比例缩放到边框尺寸,此时因为比例固定,所以每张图片尺寸都不同,但都有一条片跟边框尺寸拉平相等。
      比如一个batch的图片可能尺寸会变成(300, 256),(300, 284),(240,300)这样的形式。

    3. 图片然后经过dataloader的collate_fn,对一个batch的图片取最大外沿,进行padding变成相同尺寸的图片。
      由于transform时所有图片已有一条边靠近边框尺寸,所以取所有图片最大外沿结果基本都是边框尺寸,比如一个batch的图片会变成
      (300,300),(300,300),(300,300)然后堆叠成(3,300,300), 所以最终进行训练的图片基本都是以边框尺寸进行训练。

    关于训练时候的路径问题

    1. 经常会产生路径找不到的情况,比较合理的解决方案是:

      • 数据集的root路径,尽可能采用绝对路径,即以/开头的绝对路径。
      • 项目的root路径尽可能加到sys.path中去。
    2. 如果有报错说部分路径无法导入,一般有2种可能性:

      • 根目录路径不在sys.path中,可通过添加根目录路径到sys.path并结合根目录下一级目录可见的原则,实现大部分文件的导入。
      • 被导入的module中含有错误的导入,需要修正这些错误,才能解决报错
      • 存在交叉导入:即A要导入B,同时B也要导入A,此时有可能产生错误,需要解除交叉导入才能解决报错。

    关于归一化和标准化

    1. 常见训练所采用的mean,std并不是跟训练数据集相关,而是跟基模型所训练的数据集相关,这是
      因为这些训练都是在基模型的基础上进行finetunning做迁移学习来训练的。
      由于pytorch的基模型基本都是在imagenet中训练的,所以这些mean, std都是imagenet的参数。
      而caffe的基模型虽然也是在imagenet中训练的,但因为处理方式不同所以std取成了1(待在caffe中确认原因)
      比如:

      • 来自pytorch的基模型:[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]
      • 来自caffe的基模型:[123.675, 116.28, 103.53], std=[1, 1, 1]
    2. 如果在一个数据集上从头训练,则需要事先计算他的mean, std。但要注意mean,std的数值顺序到底是BGR顺序还是RGB顺序。

    3. pytorch自己的transform往往是先归一化再标准化,即:
      img = (img/255 - mean) / std
      所以事先准备的mean和std都是很小的值,在-1,1之间

    4. 后边看到一些其他例子,比如mmdetection里边对其他数据集的提供的mean, std值较大。
      说明对数据的处理是只做了标准化,即:
      img = (img - mean)/std
      所以实现准备的mean和std都是比较大的数值,在0-255之间

    5. 总结一下就是:

      • 如果是用现有模型参数做迁移学习:采用的mean/std需要跟原来基模型一样,数据预处理也要按这种方式。
        比如采用caffe/pytorch基模型,则都是只做标准化是img = (img - mena) / std
      • 如果从头开始训练:则采用的mean/std跟实际img的预处理方式必须一样。
        比如采用在0-1之间的小的mean, std,则实际数据处理也要要归一化+标准化。
        而采用大的mean,std,则实际数据处理也只要做标准化。

    关于卷积的取整方式

    1. pytorch中conv, maxpool在计算输出w,h尺寸时,默认的取整方式都是下取整(floor)。
      唯一不同的是,maxpool可以手动设置成上取整即ceil mode,但conv不能手动设置,也就只能下取整。

    2. conv, maxpool两者计算输出尺寸的公式一样

      • 没有dilation时,w’ = (w - ksize + 2p)/s + 1
      • 有diliation时,相当于ksize被扩大,此时
        w’ = (w - d(ksize-1) +2p -1)/s + 1

    关于神经网络的前向计算和反向传播在pytorch中的对应

    1. 前向传播:就是模型一层一层计算,output = model(img)
    2. 损失计算:采用pytorch自带的nn.CrossEntropyLoss(y_pred, y_label),则不需要手动增加softmax,
      也不需要手动把label转换成独热编码。因为这两部分都被写在pytorch自带的交叉熵函数对象内部了。
    3. 损失反向传播:必须针对损失的标量进行,也就是losses先做规约缩减(reduction=‘mean’),然后才能
      loss.backward(),即这里loss是一个标量值,pytorch对这个标量内部梯度会自动获取,并反向传播。
    4. 优化器更新权值:必须采用optimizer.step()完成
    5. 额外要求:必须增加一句优化器梯度清零,optimizer.zero_grad(),这句必须放在backward之前,
      确保变量的梯度不会一直累加,而是每个batch独立计算,一个batch结束就清零一次。
      (自己写的神经网络,梯度是整个batch一起算,不需要累加,计算以后直接赋值,所以也就不需要清零了。)

    关于在GPU训练

    1. 如果要在GPU训练,只需要3步

      • 创建设备device:
      • 模型送入device
      • batch data送入device: 注意这里理论上只要img送入device就可以,因为跟model相关的计算只需要img输入
    2. 并行式GPU训练并不一定比单GPU快,相反对于一些比较小的模型,单GPU的速度远超过并行式训练的速度。
      可能因为并行式训练需要让数据在GPU之间搬运造成时间损耗,同时python的并行式训练并不是真正的并行,
      而是在同一时间下只有一块GPU运行的假并行,只是能利用多GPU内存而不能利用多GPU算力的假并行。

    3. 分布式训练才是真正的多GPU算力并行训练。

    关于如何设置DataLoader

    1. 对常规的数据集,如果图片尺寸都是一样的,那么直接使用pytorch默认DataLoader就可以。

    2. 对数据集中img的尺寸不一样的,由于dataloader需要对img进行堆叠,此时图片尺寸不同无法直接堆叠,
      则需要自定义collate_fn对img进行堆叠,同时处理那些labels/bboxes/segments是堆叠还是放入list.

    3. pytorch默认的collate_fn设置不是None而是default_collate_fn,所以即使不用collate_fn
      选项,也不要去把collate_fn设置为None,这会导致collate_fn找不到可用的函数导致错误。
      (从这个角度说,pytorch的官方英文文档有问题,注明DataLoader的collate_fn默认=None,
      但实际上collate_fn的默认=default_collate_fn。)

    关于contiguous的问题

    1. numpy和pytorch都有可能产生in_contiguous的问题。主要都是在transpose(),permute()之后会发生。
      参考:https://discuss.pytorch.org/t/negative-strides-of-numpy-array-with-torch-dataloader/28769

    2. 在numpy数据下的报错形式是:ValueError: some of the strides of a given numpy array are negative.
      This is currently not supported, but will be added in future releases.

    3. 在tensor数据下的报错形式是:
      可通过t1.is_contiguous()查看到是否连续。

    4. 解决方案;

      • 对于numpy: img = np.ascontiguousarray(img)
      • 对于tensor: img = img.contiguous()

    关于在dataset的__getitem__()中增加断点导致程序崩溃的问题

    1. 现象:如果模型和数据送入GPU,dataloader会调用dataset的__getitem__函数获取数据进行堆叠,
      此时如果在__getitem__里边有自定义断点,会造成系统警告且暂停训练。
    2. 解决方案:取消断点后模型训练/验证就正常了。而如果想要调试__getitem__里边的语法,可以设置
      额外的语句img, label = dataset[0]来进入__getitem__,或者next(iter(dataloader))

    关于预训练模型的加载

    1. 预训练模型加载需要分两步:第一步加载checkpoint文件,第二部加载state_dict,且第二步需要去除多余的key以及去除param size不匹配的key.
    2. 对于state_dict不匹配的问题,一般两种解决方案:
      • 直接修改预训练模型的最后一部分,然后加载参数时过滤不匹配的key
      • 自己创建整个模型,然后加载参数时过滤不匹配的key
    3. 预训练模型的参数如果放在默认.torch/model或者.cache/里边,则作为隐藏文件夹中的文件,无法通过os.path.isfile()检查出来,会报错找不到该文件。
      所以,虽然能够加载但无法检出。
      也可以把参数移到自定义文件夹去,就可以通过os.path.isfile()检测到了。

    关于常规分类问题和物体检测中的分类问题的差异?

    1. 常规分类问题是对一张图片作为最小个体进行分类;而物体检测问题中的分类是以一张图片中的每一个bbox进行分类。
      因此对于物体检测问题本质上一张图片是多个分类问题的集合。

    2. 因此常规分类问题是一张img对应一个独立label, 1个batch的数据为多张img对应多个独立label,1个batch计算一次loss,
      所以需要把一个batch的label组合成一组,相当于1个batch就是一组img对应一组label。
      因此分类的一个batch计算本质上相当于检测问题的一张图片计算。

    3. 但是检测分类问题是一张img有一组bbox对应一组label,1个batch的数据为多张img拥有多组bbox对应多组label,每组bbox,label完成一次loss计算。
      因此检测的一个batch计算本质上相当于每张图片是一次分类batch,多张图片就要手动进行多次img循环计算,循环的主体就是常规分类问题的一个batch。
      这样理解的话,就可以统一常规分类问题和检测中的分类问题了:
      - 常规分类问题需要把labels组合成一组,变成一个标准计算。
      - 检测分类问题需要循环计算每张img,而每张img计算相当于一次常规分类问题的batch计算。

    关于卷积核的作用

    1. 结论:

      • 浅层卷积核主要过滤边缘、纹理的初级信息;
      • 中层卷积核就开始学习小物体;
      • 深层卷积核开始学习大物体;
    2. 证明:参考https://www.leiphone.com/news/201808/DB6WARlVGdm4cqgk.html
      可以看到单层(也就相当于浅层)卷积核收敛以后的输出图片,类似于sobel算子,是对图片进行某个方向的边缘过滤。
      也就是说明了浅层神经网络主要是学习边缘、纹理等初级信息

    关于全连接层的参数过多如何解决的问题

    1. 全连接层所占参数一般占了一个模型总参数个数的80%以上,所以为了减小模型所占内存,需要优化全连接

    2. 替换方式1:resnet/resnext的最后一部分就是这样实现。
      用一个adaptiveAvgpooling(1), 先把卷积的每个通道收缩为1,即(c,h,w)->(c,1,1),再reshape后接一个简单全连接linear(c, 10)即可
      变换前reshape后需要至少2个全连接(chw->256),再(256->10),参数总量256chw + 25610;
      avgpool变换后reshape后只需要1个全连接(256->10),参数总量为25610, 减少了256chw个参数。

                                    </div>
                <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                    </div>
  • 相关阅读:
    Error (0xc0000225) installing Windows 8 R2 on VirtualBox
    网页宽高自适应大小
    C# Java DES加密解密
    JS获取DropDownList的value值与text值
    用Aspose.Cells控件读取Excel
    Extending your SharePoint 2007 site with Microsoft ASP.NET AJAX 3.5
    页面自定义拖拽布局
    OutLook 2010 收件箱子文件夹收到新邮件时没有桌面通知
    PeopleEditor的取值及赋值
    deprecate (声明不赞成)
  • 原文地址:https://www.cnblogs.com/sdu20112013/p/11981793.html
Copyright © 2011-2022 走看看