C^3-Framwork学习相关知识点记录

数据读入部分

主要指datasets内的程序

torchvision.transforms用法介绍

PyTorch源码解读之torch.utils.data.DataLoader
__init__中的几个重要的输入:1、dataset,这个就是PyTorch已有的数据读取接口(比如torchvision.datasets.ImageFolder)或者自定义的数据接口的输出,该输出要么是torch.utils.data.Dataset类的对象,要么是继承自torch.utils.data.Dataset类的自定义类的对象。2、batch_size,根据具体情况设置即可。3、shuffle,一般在训练数据中会采用。4、collate_fn,是用来处理不同情况下的输入dataset的封装,一般采用默认即可,除非你自定义的数据读取输出非常少见。5、batch_sampler,从注释可以看出,其和batch_size、shuffle等参数是互斥的,一般采用默认。6、sampler,从代码可以看出,其和shuffle是互斥的,一般默认即可。7、num_workers,从注释可以看出这个参数必须大于等于0,0的话表示数据导入在主进程中进行,其他大于0的数表示通过多个进程来导入数据,可以加快数据导入速度。8、pin_memory,注释写得很清楚了: pin_memory (bool, optional): If True, the data loader will copy tensors into CUDA pinned memory before returning them. 也就是一个数据拷贝的问题。9、timeout,是用来设置数据读取的超时时间的,但超过这个时间还没读取到数据的话就会报错。

模型部分

nn.Sequential
A sequential container. Modules will be added to it in the order they are passed in the constructor. Alternatively, an ordered dict of modules can also be passed in.

一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。

Example of using Sequential

    model = nn.Sequential(
              nn.Conv2d(1,20,5),
              nn.ReLU(),
              nn.Conv2d(20,64,5),
              nn.ReLU()
            )

    # Example of using Sequential with OrderedDict
    model = nn.Sequential(OrderedDict([
              ('conv1', nn.Conv2d(1,20,5)),
              ('relu1', nn.ReLU()),
              ('conv2', nn.Conv2d(20,64,5)),
              ('relu2', nn.ReLU())
            ]))

先看一下初始化函数__init__,在初始化函数中,首先是if条件判断,如果传入的参数为1个,并且类型为OrderedDict,通过字典索引的方式将子模块添加到self._module中,否则,通过for循环遍历参数,将所有的子模块添加到self._module中。注意,Sequential模块的初始换函数没有异常处理,所以在写的时候要注意。

接下来看一下forward函数的实现:
因为每一个module都继承于nn.Module,都会实现__call__与forward函数,所以forward函数中通过for循环依次调用添加到self._module中的子模块,最后输出经过所有神经网络层的结果

下面是简单的三层网络结构的例子:

hyper parameters
in_dim=1
n_hidden_1=1
n_hidden_2=1
out_dim=1

class Net(nn.Module):
def init(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super().init()

  	self.layer = nn.Sequential(
        nn.Linear(in_dim, n_hidden_1), 
        nn.ReLU(True),
        nn.Linear(n_hidden_1, n_hidden_2),
        nn.ReLU(True),
        # 最后一层不需要添加激活函数
        nn.Linear(n_hidden_2, out_dim)
         )

def forward(self, x):
  	x = self.layer(x)
  	return x

上面的代码就是通过Squential将网络层和激活函数结合起来,输出激活后的网络节点。

transforms.ToTensor()作用
ToTensor()将shape为(H, W, C)的nump.ndarray或img转为shape为(C, H, W)的tensor,其将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。具体可参见如下代码:

import torchvision.transforms as transforms
import numpy as np
from __future__ import print_function

定义转换方式,transforms.Compose将多个转换函数组合起来使用

transform1 = transforms.Compose([transforms.ToTensor()])  #归一化到(0,1),简单直接除以255

定义一个数组

d1 = [1,2,3,4,5,6]
d2 = [4,5,6,7,8,9]
d3 = [7,8,9,10,11,14]
d4 = [11,12,13,14,15,15]
d5 = [d1,d2,d3,d4]
d = np.array([d5,d5,d5],dtype=np.float32)
d_t = np.transpose(d,(1,2,0)) # 转置为类似图像的shape,(H,W,C),作为transform的输入
查看d的shape
print('d.shape: ',d.shape, '\n', 'd_t.shape: ', d_t.shape)
 # 输出
 d.shape: (3, 4, 6) 
 d_t.shape: (4, 6, 3)

d_t_trans = transform1(d_t) # 直接使用函数归一化

**手动归一化,下面的两个步骤可以在源码里面找到**
d_t_temp = torch.from_numpy(d_t.transpose((2,0,1)))
d_t_trans_man = d_t_temp.float().div(255)

print(d_t_trans.equal(d_t_trans_man))
 # 输出
 True

transforms.Normalize()作用
在transforms.Compose([transforms.ToTensor()])中加入transforms.Normalize(),如下所示:transforms.Compose([transforms.ToTensor(),transforms.Normalize(std=(0.5,0.5,0.5),mean=(0.5,0.5,0.5))]),则其作用就是先将输入归一化到(0,1),再使用公式”(x-mean)/std”,将每个元素分布到(-1,1)

transform2 = transforms.Compose([transforms.ToTensor(),transforms.Normalize(std=(0.5,0.5,0.5),mean=(0.5,0.5,0.5))])# 归一化到(0,1)之后,再 (x-mean)/std,归一化到(-1,1),数据中存在大于mean和小于mean

d_t_trans_2 = transform2(d_t)
d_t_temp1 = torch.from_numpy(d_t.transpose((2,0,1)))
d_t_temp2 = d_t_temp1.float().div(255)
d_t_trans_man2 = d_t_temp2.sub_(0.5).div_(0.5)
print(d_t_trans_2.equal(d_t_trans_man2))
 #输出
 True

数据处理

standard_transforms.ToPILImage
ToPILImage顾名思义是从Tensor到PIL Image的过程,和前面ToTensor类的相反的操作。

img.mode
在这里插入图片描述
torch.no_grad
requires_grad
Variable变量的requires_grad的属性默认为False,若一个节点requires_grad被设置为True,那么所有依赖它的节点的requires_grad都为True。

x=Variable(torch.ones(1))
w=Variable(torch.ones(1),requires_grad=True)
y=x*w
x.requires_grad,w.requires_grad,y.requires_grad
Out[23]: (False, True, True)

y依赖于w,w的requires_grad=True,因此y的requires_grad=True (类似or操作)

Volatile(更改为torch.no_grad)
volatile=True是Variable的另一个重要的标识,它能够将所有依赖它的节点全部设为volatile=True,其优先级比requires_grad=True高。因而volatile=True的节点不会求导,即使requires_grad=True,也不会进行反向传播,对于不需要反向传播的情景(inference,测试推断),该参数可以实现一定速度的提升,并节省一半的显存,因为其不需要保存梯度。
前方高能预警:如果你看完了前面volatile,请及时把它从你的脑海中擦除掉,因为
UserWarning: volatile was removed (Variable.volatile is always False)
该属性已经在0.4版本中被移除了,并提示你可以使用with torch.no_grad()代替该功能

x = torch.tensor([1], requires_grad=True)with torch.no_grad():
...    y = x * 2
      y.requires_grad
False
@torch.no_grad()
def doubler(x):
      return x * 2
z = doubler(x)
z.requires_grad
Fals

.mat文件
mat文件是matlab的数据存储的标准格式。mat文件是标准的二进制文件,还可以ASCII码形式保存和加载,在MATLAB中打开显示类似于单行EXCEL表格。

工程理论解读(MCNN)

参考:人群计数:Single-Image Crowd Counting via Multi-Column Convolutional Neural Network(CVPR2016)

人群计数的通常的方法大致可以分为三种:
1 )行人检测 : 这种方法比较直接,在人群较稀疏的场景中,通过检测视频中的每一个行人,进而得到人群计数的结果,一般是用基于外观和运动特征的boosting,贝叶斯模型为基础的分割,或集成的自顶向下和自底向上的处理,这种方法在人群拥挤情况下不大奏效,需要运用到基于部件模型(如DPM)的检测器来克服人群拥挤遮挡的问题。
2)视觉特征轨迹聚类:对于视频监控,一般用KLT跟踪器和聚类的方法,通过轨迹聚类得到的数目来估计人数。
3)基于特征的回归: 建立图像特征和图像人数的回归模型, 通过测量图像特征从而估计场景中的人数。由于拥挤情况下采用直接法容易受到遮挡等难点问题的影响,而间接法从人群的整体特征出发,具有大规模人群计数的能力。

基于特征的回归一般分为以下3个步骤:
1)前景分割:前景(行人或人群)分割的目的是将人群从图像中分割出来便于后面的特征提取,分割性能的好坏直接关系的最终的计数精度,因此这是限制传统算法性能的一个重要因素。常用的分割算法有:光流法、混合动态纹理、小波分析 、背景差分等。
2)特征提取:从分割得到的前景提取各种不同的底层特征,常用的特征有:人群面积和周长、边缘信息、纹理特征、闵可夫斯基维度等。
3)人数回归:将提取到的特征回归到图像中的人数。常用的回归方法有:线性回归、分段线性回归、脊回归、高斯过程回归等[1]。

当前阶段人群计数的主要问题有以下几点:
在大多数现有的工作中,前景分割是必不可少的,但前景分割是项艰巨任务;人群的密度和分布会有显著变化,因此传统的基于目标检测的模型很难work well;需要一种有效的特征来针对图像中人群规模可能有显著变化的情况。
基于以上问题,作者提出了一个基于CNN的新框架用于任意单幅图像上的人群计数。MCNN包含了三列具有不同滤波器大小的卷积神经网络。所做贡献如下:
1)多列架构的原因是:三列对应于不同大小的感受野(大,中,小),使每个列卷积神经网络的功能对由于透视或不同的图像分辨率造成的人/头大小变化是自适应的(因此,整体网络是强大的)。
2)用一个1*1滤波器的卷积层代替了完全连接的层,因此模型的输入图像可以是任意大小的,避免了失真。网络的直接输出是一个人群密度估计图,从中可以得到的整体计数。
3)收集了一个新的数据集用于人群计数方法的评价。比现有的数据集包含更复杂的情况,能更好地测试方法性能,1198张图,330,165精确标定的人头。数据集分A和B两个部分,A是从互联网上随机找的图,B是上海的闹市截取图,如图5所示为A、B部分图。
在这里插入图片描述

给定一张图像,用CNNs来估计人数,一般有两种方案:一是输入图像,输出估计的人头数目;二是输出的时人群密度图(每平方米多少人),然后再通过积分求总人数。作者支持第二种,有以下两点原因:
1)密度图保留更多的信息。与人群的总数相比,密度图给出了在给定图像中人群的空间分布,这样的分布信息在许多应用中是有用的。例如,如果一个小区域的密度比其他区域的密度高得多,它可能表明一些异常发生在那里。
2)在通过一个CNN模型学习密度图时,学习到的滤波器更适应于不同大小的头,因此更适合于有透视效果显着变化的任意输入。所以这些滤波器具有更多的语义,提高了人群计数的准确性。

在这里插入图片描述
在这里插入图片描述
MCNN主要是受到MDNNs[6] 在图像分类上取得成功的启发而提出来的。MCNN网络的每一列并行的子网络深度相同,但是滤波器的大小不同(大,中,小),因此每一列子网络的感受野不同,能够抓住不同大小人头的特征,最后将三列子网络的特征图做线性加权(由1x1的卷积完成)得到该图像的人群密度图,类似模型融合的思想。采用了2*2的max-pooling和ReLU激活函数。(注意,因为这里用到了两次max-pooling,所以需要先对训练样本也缩小到1/4,再生成对应的密度图ground truth

在这里插入图片描述
在这里插入图片描述
由于训练样本有限以及深度神经网路存在梯度弥散问题,会对参数学习造成困扰。作者借鉴RBM预训练的思想,通过直接将第四层卷积层的输出映射成密度图,分别对每一列CNN进行预训练。然后使用这些预训练的参数对MCNN的网络参数进行初始化,同时对所有参数进行微调。

MCNN的一个优势在于能学习到不同大小人头对应的密度图。因此,如果该模型用一个包含各种大小人头的大数据集来训练,则该模型可以很容易地适应(或迁移)到另一个人头大小是一些特定的尺寸的数据集。如果目标域只包含少量的训练样本,可以简单地将MCNN的每一列前几层固定,只有微调最后的少量卷积层。这样固定前几层使在源域中学习的知识可以被保留,微调后几层很大程度上降低了模型适应目标域的计算复杂度。

该工程实际应用可参看论文
ADEEPLY-RECURSIVE CONVOLUTIONAL NETWORK FOR CROWD COUNTING
论文简介:
人群计数要实现落地的话,必须要考虑计算资源受限的问题,当前很多模型的参数量过多需要占用较多存储空间,计算量过大计算耗时严重,考虑到实际应用过程中,比如嵌入式应用等,2018年这篇论[10]提出的基于递归结构的残差网络模型,较好地处理了模型性能和参数之间的权衡问题。网络结构如下图所示,残差连接可以减少深度网络的梯度弥散,这样便通过加深网络提高性能,而在高层语义特征层上采用参数复用的方式,可以避免因网络加深带来的参数量增大,同时参数复用使提取的高层语义特征更加稳定。从Table 1中可以看出随着网络卷积层数目从14到26加深,MAE和MSE在逐步下降,DR-ResNet跟ResNet-26一样的深度,但模型参数量仅为ResNet-26的一半,DR-ResNet在人数规模较大的Part_A上MAE略低于ResNet-26,但MSE却更好,表明该网络结构改善了模型的泛化能力。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值