pytorch学习笔记

一 pytorch数据结构

Tensor有自己的数据类型,也有CPU和GPU版本的Tensor。torch.set_default_tensor_type修改指定tensor类型,默认是torch.float32 or torch.float。
每个元素占用32bit/8=4Byte内存。

  1. torch和numpy
    二者用法类似,二者可以互相转换。
    有共享内存和不共享内存的情况
1.numpy转tensor
(1)torch.Tensor()
import torch as t
a = np.ones([2, 3],dtype=np.float32)
b = torch.Tensor(a)
#numpy默认类型为float64,在tensor中是DoubleTensor,当dtype类型不一致,只复制,不共享内存

a = np.ones([2, 3])
# 注意和上面的a的区别(dtype不是float32)
a.dtype  #dtype('float64')
e = t.Tensor(a) # 此处进行拷贝,不共享内存
e.dtype  #torch.float32

(2)torch.from_numpy(a)
c = t.from_numpy(a) # 注意c的类型(DoubleTensor)
c

#结果:
tensor([[  1., 100.,   1.],
        [  1.,   1.,   1.]], dtype=torch.float64)
        
a[0, 1] = 100

b # b与a不共享内存,所以即使a改变了,b也不变

c # c与a共享内存
#结果:
tensor([[  1., 100.,   1.],
        [  1.,   1.,   1.]], dtype=torch.float64)

(3)torch.tensor()
不论输入的类型是什么,t.tensor都会进行数据拷贝,不会共享内存
2.tensor转numpy
c = b.numpy()


2.广播法则(broadcast)

广播法则(broadcast)是科学运算中经常使用的一个技巧,它在快速执行向量化的同时不会占用额外的内存/显存。
Numpy的广播法则定义如下:

  • 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐
  • 两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算
  • 当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状

PyTorch当前已经支持了自动广播法则,但是笔者还是建议读者通过以下两个函数的组合手动实现广播法则,这样更直观,更不易出错:

  • unsqueeze或者view,或者tensor[None],:为数据某一维的形状补1,实现法则1
  • expand或者expand_as,重复数组,实现法则3;该操作不会复制数组,所以不会占用额外的空间。

注意,repeat实现与expand相类似的功能,但是repeat会把相同数据复制多份,因此会占用额外的空间。

a = t.ones(3, 2)
b = t.zeros(2, 3,1)

1.自动广播法则
# 第一步:a是2维,b是3维,所以先在较小的a前面补1 ,
#               即:a.unsqueeze(0),a的形状变成(1,3,2),b的形状是(2,3,1),
# 第二步:   a和b在第一维和第三维形状不一样,其中一个为1 ,
#               可以利用广播法则扩展,两个形状都变成了(2,3,2)
a+b

#结果;
tensor([[[1., 1.],
         [1., 1.],
         [1., 1.]],

        [[1., 1.],
         [1., 1.],
         [1., 1.]]])

2.手动广播

a.view(1,2,3).expand(2,2,3)+b.expand(2,3,2)
#等价:a[None].expand(2, 3, 2) + b.expand(2,3,2)

#结果:
tensor([[[1., 1.],
         [1., 1.],
         [1., 1.]],

        [[1., 1.],
         [1., 1.],
         [1., 1.]]])
         
# expand不会占用额外空间,只会在需要的时候才扩充,可极大节省内存
e = a.unsqueeze(0).expand(10000000000000, 3,2)
print(e.shape)

#torch.Size([10000000000000, 3, 2])

3.Tensor内部结构
tensor的数据结构如图3-1所示。tensor分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状(size)、步长(stride)、数据类型(type)等信息,而真正的数据则保存成连续数组。由于数据动辄成千上万,因此信息区元素占用内存较少,主要内存占用则取决于tensor中元素的数目,也即存储区的大小。

一般来说一个tensor有着与之相对应的storage, storage是在data之上封装的接口,便于使用,而不同tensor的头信息一般不同,但却可能使用相同的数据。

a = t.arange(0, 6)
a.storage()
b = a.view(2, 3)
b.storage() #与a.storage()一样
#0
 1
 2
 3
 4
 5
[torch.LongStorage of size 6]


# a改变,b也随之改变,因为他们共享storage
a[1] = 100
b
#结果:
tensor([[  0, 100,   2],
        [  3,   4,   5]])
        
# 一个对象的id值可以看作它在内存中的地址
# storage的内存地址一样,即是同一个storage
id(b.storage()) == id(a.storage())
# 一个对象的id值可以看作它在内存中的地址
a.dtype
#torch.int64
c.data_ptr(), a.data_ptr() # data_ptr返回tensor首元素的内存地址
# 可以看出相差16,这是因为2*8=16--相差两个元素,每个元素占8个字节(int64)


c[0] = -100 # c[0]的内存地址对应a[2]的内存地址
a

#结果:
tensor([   0,  100, -100,    3,    4,    5])

可见绝大多数操作并不修改tensor的数据,而只是修改了tensor的头信息。这种做法更节省内存,同时提升了处理速度。在使用中需要注意。
此外有些操作会导致tensor不连续,这时需调用`tensor.contiguous`方法将它们变成连续的数据,该方法会使数据复制一份,不再与原来的数据共享storage。

e = b[::2, ::2] # 隔2行/列取一个元素
id(e.storage()) == id(a.storage()) #True
print(b[:, ::2]) #隔2列取元素,跳过第二列
print(b[::3, :]) #隔2行取元素 由于只有两行,取完第一行,只要>1,就没的取了,只输出第一行

#结果:
tensor([[6666., -100.],
        [   3.,    5.]])
tensor([[6666.,  100., -100.]])
tensor([[6666., -100.]])
print(e.storage())
b.stride(), e.stride()

#结果:
tensor([[6666.,  100., -100.],
        [   3.,    4.,    5.]])
tensor([[6666., -100.]])
 6666.0
 100.0
 -100.0
 3.0
 4.0
 5.0
[torch.FloatStorage of size 6]
((3, 1), (6, 2))


print(e.is_contiguous())
print(b.is_contiguous())

#False
True


另外读者可以思考一下,之前说过的高级索引一般不共享stroage,而普通索引共享storage,这是为什么?(提示:普通索引可以通过只修改tensor的offset,stride和size,而不修改storage来实现)。

GPU/CPU
tensor可以很随意的在gpu/cpu上传输。使用tensor.cuda(device_id)或者tensor.cpu()。另外一个更通用的方法是tensor.to(device)
持久化
Tensor的保存和加载十分的简单,使用t.save和t.load即可完成相应的功能。在save/load时可指定使用的pickle模块,在load时还可将GPU tensor映射到CPU或其它GPU上。
向量化
实际使用中应尽量调用内建函数(buildin-function),这些函数底层由C/C++实现,能通过执行底层优化实现高效计算。因此在平时写代码时,就应养成向量化的思维习惯,千万避免对较大的tensor进行逐元素遍历。

线性代数
PyTorch的线性函数主要封装了Blas和Lapack,其用法和接口都与之类似。常用的线性代数函数如表3-7所示。

表3-7: 常用的线性代数函数

函数	功能
trace	对角线元素之和(矩阵的迹)
diag	对角线元素
triu/tril	矩阵的上三角/下三角,可指定偏移量
mm/bmm	矩阵乘法,batch的矩阵乘法
addmm/addbmm/addmv/addr/badbmm..	矩阵运算
t	转置
dot/cross	内积/外积
inverse	求逆矩阵
svd	奇异值分解
具体使用说明请参见官方文档[^3],需要注意的是,矩阵的转置会导致存储空间不连续,需调用它的.contiguous方法将其转为连续

a = t.linspace(0, 15, 6).view(2, 3)
b = a.t()
print(a)
print(b.is_contiguous())
print(b)
print(b.storage())

#结果:
tensor([[ 0.,  3.,  6.],
        [ 9., 12., 15.]])
False
tensor([[ 0.,  9.],
        [ 3., 12.],
        [ 6., 15.]])
 0.0
 3.0
 6.0
 9.0
 12.0
 15.0
[torch.FloatStorage of size 6]

print(b.contiguous().storage())
print(b.contiguous().is_contiguous())

#结果:
 0.0
 9.0
 3.0
 12.0
 6.0
 15.0
[torch.FloatStorage of size 6]
True



a = t.Tensor([[1,2]])
b=t.Tensor([[3,4]])
a.mm(b.t())

#结果:
tensor([[11.]])
二 神经网络torch.nn
torch.nn是专门为深度学习而设计的模块。
torch.nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),
也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层
包含5个模块
from .modules import *
from .parameter import Parameter
from .parallel import DataParallel
from . import init
from . import utils

一.Containers(容器):
class torch.nn.Module
所有网络的基类。

1.add_module(name, module)

train(mode=True)
将module设置为 training mode。

仅仅当模型中有Dropout和BatchNorm是才会有影响。

zero_grad()
将module中的所有模型参数的梯度设置为0.
modules()
返回一个包含 当前模型 所有模块的迭代器。
你的模型也应该继承这个类。

Modules也可以包含其它Modules,允许使用树结构嵌入他们。你可以将子模块赋值给模型属性。

2.class torch.nn.Sequential(* args)
一个时序容器。Modules 会以他们传入的顺序被添加到容器中。当然,也可以传入一个OrderedDict。

# Sequential的三种写法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())

net2 = nn.Sequential(
        nn.Conv2d(3, 3, 3),
        nn.BatchNorm2d(3),
        nn.ReLU()
        )

from collections import OrderedDict
net3= nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(3, 3, 3)),
          ('bn1', nn.BatchNorm2d(3)),
          ('relu1', nn.ReLU())
        ]))
print('net1:', net1)
print('net2:', net2)
print('net3:', net3)

#结果:
('net1:', Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
))
('net2:', Sequential(
  (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU()
))
('net3:', Sequential(
  (conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu1): ReLU()
))


3. class torch.nn.ModuleList(modules=None)[source]

1.全连接层
class torch.nn.Linear(in_features, out_features, bias=True)
对输入数据做线性变换:\(y = Ax + b\)

参数:

in_features - 每个输入样本的大小
out_features - 每个输出样本的大小
bias - 若设置为False,这层不会学习偏置。默认值:True

全连接层的实现非常简单,其代码量不超过10行,但需注意以下几点:

自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,
即super(Linear, self).__init__()或nn.Module.__init__(self),
推荐使用第一种用法,尽管第二种写法更直观。
在构造函数__init__中必须自己定义可学习的参数,并封装成Parameter,
如在本例中我们把w和b封装成parameter。parameter是一种特殊的Tensor,
但其默认需要求导(requires_grad=True),
class torch.nn.Parameter()
Variable的一种,常被用于模块参数(module parameter)。

(* Parameter:Parameters 是 Variable的子类。
Paramenters和Modules一起使用的时候会有一些特殊的属性,
即:当Paramenters赋值给Module的属性的时候,他会自动的被加到 Module的 参数列表中
(即:会出现在 parameters() 迭代器中)。将Varibale赋值给Module属性则不会有这样的影响。 这样做的原因是:
我们有时候会需要缓存一些临时的状态(state), 
比如:模型中RNN的最后一个隐状态。如果没有Parameter这个类的话,那么这些临时变量也会注册成为模型变量。
Variable 与 Parameter的另一个不同之处在于,Parameter不能被 volatile
(即:无法设置volatile=True)而且默认requires_grad=True。Variable默认requires_grad=False。

参数说明:

data (Tensor) – parameter tensor.

requires_grad (bool, optional) – 默认为True,在BP的过程中会对其求微分。)

查看Parameter类的源代码。forward函数实现前向传播过程,其输入可以是一个或多个tensor。
无需写反向传播函数,nn.Module能够利用autograd自动实现反向传播,这点比Function简单许多。
使用时,直观上可将layer看成数学概念中的函数,调用layer(input)即可得到input对应的结果。
它等价于layers.__call__(input),在__call__函数中,主要调用的是 layer.forward(x),
另外还对钩子做了一些处理。所以在实际使用中应尽量使用layer(x)而不是使用layer.forward(x)
Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器,
前者会给每个parameter都附上名字,使其更具有辨识度。
可见利用Module实现的全连接层,比利用Function实现的更为简单,
因其不再需要写反向传播函数。
2 卷积层

#class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
#二维卷积层, 输入的尺度是(N, C_in,H,W),输出尺度(N,C_out,H_out,W_out)
# 说明
# bigotimes: 表示二维的相关系数计算 stride: 控制相关系数的计算步长 
# dilation: 用于控制内核点之间的距离,详细描述在这里
# groups: 控制输入和输出之间的连接: group=1,输出是所有的输入的卷积;group=2,
#此时相当于有并排的两个卷积层,每个卷积层计算输入通道的一半,并且产生的输出是输出通道的一半,随后将这两个输出连接起来。

# 参数kernel_size,stride,padding,dilation也可以是一个int的数据,
#此时卷积height和width值相同;也可以是一个tuple数组,tuple的第一维度表示height的数值,tuple的第二维度表示width的数值

# Parameters:

# in_channels(int) – 输入信号的通道
# out_channels(int) – 卷积产生的通道
# kerner_size(int or tuple) - 卷积核的尺寸
# stride(int or tuple, optional) - 卷积步长
# padding(int or tuple, optional) - 输入的每一条边补充0的层数
# dilation(int or tuple, optional) – 卷积核元素之间的间距
# groups(int, optional) – 从输入通道到输出通道的阻塞连接数
# bias(bool, optional) - 如果bias=True,添加偏置
# shape:
# input: (N,C_in,H_in,W_in) 
# output: (N,C_out,H_out,W_out)

# 变量:
# weight(tensor) - 卷积的权重,大小是(out_channels, in_channels,kernel_size)
# bias(tensor) - 卷积的偏置系数,大小是(out_channel)

# H_out = (H-kernel_size.[0]+2padding)/stride + 1 
input = t.autograd.Variable(t.randn(20, 16, 50, 100))   
m = nn.Conv2d(16, 33, 3, stride=2)   # 24 = (50-3)/2+1
output = m(input)
print(output.size())
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))  # 28 = (50-3+2*4)/2 + 1
output = m(input)
print(output.size())

#感受野(receptive field)和空洞卷积(dilated convolution)
#感受野计算: l(k) = l(k-1) + [(fk-1) * ∏Si]  {(i=1,2,...,k-1)  l(k)是第k层的感受野,fk是卷积核大小,si是步长,与padding无关}
# dilation-1 指的是kernel的间隔数量
#加入扩张卷积的filter为: filter = k + (k-1)*(dilation-1)   k是原卷积核大小
#一是可以理解为将卷积核扩展,如图卷积核为 3\times3 但是这里将卷积核变为 5\times5 即在卷积核每行每列中间加0。
#二是理解为在特征图上每隔1行或一列取数与 3\times3 卷积核进行卷积

# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1)) 
# 加入dilation的filter为7x5   3 + (3-1)*(3-1)= 7   (50+2*4-7)/2 + 1 = 26

#引入dilation的原因:
# FCN中通过pooling增大感受野缩小图像尺寸,然后通过upsampling还原图像尺寸,但是这个过程中造成了精度的损失,
# 那么为了减小这种损失理所当然想到的是去掉pooling层,然而这样就导致特征图感受野太小,因此空洞卷积应运而生

output = m(input)
print(output.size())


##结果:
torch.Size([20, 33, 24, 49])
torch.Size([20, 33, 28, 100])
torch.Size([20, 33, 26, 100])
###Pool池化层

pool = nn.AvgPool2d(2,2)
#class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
# 对于输入信号的输入通道,提供2维最大池化(max pooling)操作

# 如果输入的大小是(N,C,H,W),那么输出的大小是(N,C,H_out,W_out)和池化窗口大小(kH,kW)的关系是: 

# 如果padding不是0,会在输入的每一边添加相应数目0 
# dilation用于控制内核点之间的距离,详细描述在这里

# 参数kernel_size,stride, padding,dilation数据类型: 可以是一个int类型的数据,
# 此时卷积height和width值相同; 也可以是一个tuple数组(包含来两个int类型的数据),
# 第一个int数据表示height的数值,tuple的第二个int类型的数据表示width的数值

# 参数:
# kernel_size(int or tuple) - max pooling的窗口大小
# stride(int or tuple, optional) - max pooling的窗口移动的步长。默认值是kernel_size
# padding(int or tuple, optional) - 输入的每一条边补充0的层数
# dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数
# return_indices - 如果等于True,会返回输出最大值的序号,对于上采样操作会有帮助
# ceil_mode - 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作
# shape: 
# 输入: (N,C,H_{in},W_in) 
# 输出: (N,C,H_out,W_out) 


# class torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True)
# 对信号的输入通道,提供2维的平均池化(average pooling )
# 输入信号的大小(N,C,H,W),输出大小(N,C,H_out,W_out)和池化窗口大小(kH,kW)的关系是: 

# 如果padding不是0,会在输入的每一边添加相应数目0

# 参数:
# kernel_size(int or tuple) - 池化窗口大小
# stride(int or tuple, optional) - max pooling的窗口移动的步长。默认值是kernel_size
# padding(int or tuple, optional) - 输入的每一条边补充0的层数
# dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数
# ceil_mode - 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作
# count_include_pad - 如果等于True,计算平均池化时,将包括padding填充的0
# shape:
# input: (N,C,H_in,W_in)
# output: (N,C,H_out,W_out)
list(pool.parameters())
##BatchNorm
#class torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True) [source]
# 对小批量(mini-batch)的2d或3d输入进行批标准化(Batch Normalization)操作

# y=(x−mean[x])/(Var[x]^1/2 + ϵ)∗gamma+beta
# 在每一个小批量(mini-batch)数据中,计算输入各个维度的均值和标准差。gamma与beta是可学习的大小为C的参数向量(C为输入大小)

# 在训练时,该层计算每次输入的均值与方差,并进行移动平均。移动平均默认的动量值为0.1。

# 在验证时,训练求得的均值/方差将用于标准化验证数据。

# 参数:

# num_features: 来自期望输入的特征数,该期望输入的大小为'batch_size x num_features [x width]'
# eps: 为保证数值稳定性(分母不能趋近或取0),给分母加上的值。默认为1e-5。
# momentum: 动态均值和动态方差所使用的动量。默认为0.1。
# affine: 一个布尔值,当设为true,给该层添加可学习的仿射变换参数。
# Shape: - 输入:(N, C)或者(N, C, L) - 输出:(N, C)或者(N,C,L)(输入输出相同)


bn = nn.BatchNorm1d(4)
#初始化均值为0,方差为4
bn.weight.data = t.ones(4)*4
bn.bias.data = t.zeros(4)
print(h)
bn_out = bn(h)  #h(2x4)
print(bn_out)
bn_out.mean(0),bn_out.var(0, unbiased=False)  #输出均值和方差


#结果:
tensor([[ 0.6809, -1.0352,  1.2438, -0.8621],
        [-0.3230,  0.0332,  0.2762,  0.5024]], grad_fn=<ThAddmmBackward>)
tensor([[ 3.9999, -3.9999,  3.9999, -4.0000],
        [-3.9999,  3.9999, -3.9999,  4.0000]],
       grad_fn=<ThnnBatchNormBackward>)
(tensor([0., 0., 0., 0.], grad_fn=<MeanBackward0>),
 tensor([15.9994, 15.9994, 15.9993, 15.9997], grad_fn=<VarBackward1>))
 
 
 ## dropout

dropout = nn.Dropout(0.5)
o = dropout(bn_out)
o   #一半左右变为0


#结果:
tensor([[ 7.9998, -7.9999,  0.0000, -7.9999],
        [-0.0000,  0.0000, -7.9998,  0.0000]], grad_fn=<DropoutBackward>)
三 ResNet34示例

下面解析代码,正向传播每一步参数的计算都加了注释

from torch import nn
import torch as t
from torch.nn import functional as F


# 用子module来实现residual block,用_make_layer函数来实现layer

class ResidualBlock(nn.Module):
    def __init__(self,inchannel,outchannel,stride=1,shortcut=None):
        super(ResidualBlock,self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,3,stride,1,bias=False),  #kernel_size=3, stride=1,padding=1
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel,outchannel,3,1,1,bias=False),
            nn.BatchNorm2d(outchannel))
        self.right = shortcut
        
    def forward(self,x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)

class ResNet(nn.Module):
    def __init__(self,num_classes=1000):
        super(ResNet,self).__init__()
        self.pre = nn.Sequential(
            nn.Conv2d(3,64,7,2,3,bias=False),  #in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3
            #(1,64,112,112)
            nn.BatchNorm2d(64),    
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3,2,1))  #kernel_size=3, stride=2, padding=1
            #(1,64,56,56)
        # 重复的layer,分别有3,4,6,3个residual block
        self.layer1 = self._make_layer(64,64,3)  
        self.layer2 = self._make_layer(64,128,4,stride=2)
        self.layer3 = self._make_layer(128,256,6,stride=2)
        self.layer4 = self._make_layer(256,512,3,stride=2)
        #(1,512,7,7)
        self.fc = nn.Linear(512,num_classes)
        
     
    def _make_layer(self,inchannel,outchannel,block_num,stride=1):
        
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,1,stride,bias=False),
            nn.BatchNorm2d(outchannel))
        layers = []
        layers.append(ResidualBlock(inchannel,outchannel,stride,shortcut))
        
        for i in range(1,block_num):
            layers.append(ResidualBlock(outchannel,outchannel))
        return nn.Sequential(*layers)
    
    def forward(self, x):
        x = self.pre(x)
        
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x,7)  #(1,512,7,7)->(1,512,1,1)
        x = x.view(x.size(0), -1)   #(1,512,1,1)->(1,512)->(1,1000)
        return self.fc(x)   #(1,512)->(1,1000)
    
    
    
model = ResNet()
input = t.rand(1,3,224,224)
# print(F.avg_pool2d(t.rand(1,3,224,224),7).size()) #(1,3,32,32)
# F.avg_pool2d(input,7)   #input=x, kernel_size=7, stride=None, padding=0
# #(1,3,224,224) -> (1,3,32,32)
o = model(input)
print(o.size())

#结果:
torch.Size([1, 3, 32, 32])
torch.Size([1, 1000])
    
    
PyTorch配套的图像工具包torchvision已经实现了深度学习中大多数经典的模型,其中就包括ResNet34,读者可以通过下面两行代码使用:

from torchvision import models
model = models.resnet34()    
        
四 卷积详解
感受野(receptive field)和空洞卷积(dilated convolution)
感受野计算: l(k) = l(k-1) + [(fk-1) * ∏Si] {(i=1,2,…,k-1)
l(k)是第k层的感受野,fk是卷积核大小,si是步长,与padding无关}
dilation-1 指的是kernel的间隔数量
加入扩张卷积的filter为: filter = k + (k-1)*(dilation-1) k是原卷积核大小
一是可以理解为将卷积核扩展,如图卷积核为 3\times3 但是这里将卷积核变为 5\times5 即在卷积核每行每列中间加0。
二是理解为在特征图上每隔1行或一列取数与 3\times3 卷积核进行卷积
引入dilation的原因:FCN中通过pooling增大感受野缩小图像尺寸,然后通过upsampling还原图像尺寸,但是这个过程中造成了精度的损失,那么为了减小这种损失理所当然想到的是去掉pooling层,然而这样就导致特征图感受野太小,因此空洞卷积应运而生
较为著名的是 up-sampling 和 pooling layer 的设计。这个在 Hinton 的演讲里也一直提到过。
主要问题有:Up-sampling / pooling layer (e.g. bilinear interpolation) is deterministic. (a.k.a. not learnable)内部数据结构丢失;空间层级化信息丢失。小物体信息无法重建 (假设有四个pooling layer 则 任何小于 2^4 = 16 pixel 的物体信息将理论上无法重建。)在这样问题的存在下,语义分割问题一直处在瓶颈期无法再明显提高精度, 而 dilated convolution 的设计就良好的避免了这些问题。
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage

to_tensor = ToTensor()   #img->tensor
to_pil = ToPILImage()
lena = Image.open('imgs/lena.png')
lena

input = to_tensor(lena).unsqueeze(0)

#锐化(sharpening)卷积核 图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征
kernel = t.ones(3,3)/-9
kernel[1][1] = 1
conv = nn.Conv2d(1,1,(3,3),1,bias=False)
#class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
#二维卷积层, 输入的尺度是(N, C_in,H,W),输出尺度(N,C_out,H_out,W_out)
# 说明
# bigotimes: 表示二维的相关系数计算 stride: 控制相关系数的计算步长 
# dilation: 用于控制内核点之间的距离,详细描述在这里
# groups: 控制输入和输出之间的连接: group=1,输出是所有的输入的卷积;group=2,此时相当于有并排的两个卷积层,每个卷积层计算输入通道的一半,并且产生的输出是输出通道的一半,随后将这两个输出连接起来。

# 参数kernel_size,stride,padding,dilation也可以是一个int的数据,此时卷积height和width值相同;也可以是一个tuple数组,tuple的第一维度表示height的数值,tuple的第二维度表示width的数值

# Parameters:

# in_channels(int) – 输入信号的通道
# out_channels(int) – 卷积产生的通道
# kerner_size(int or tuple) - 卷积核的尺寸
# stride(int or tuple, optional) - 卷积步长
# padding(int or tuple, optional) - 输入的每一条边补充0的层数
# dilation(int or tuple, optional) – 卷积核元素之间的间距
# groups(int, optional) – 从输入通道到输出通道的阻塞连接数
# bias(bool, optional) - 如果bias=True,添加偏置
# shape:
# input: (N,C_in,H_in,W_in) 
# output: (N,C_out,H_out,W_out)

# 变量:
# weight(tensor) - 卷积的权重,大小是(out_channels, in_channels,kernel_size)
# bias(tensor) - 卷积的偏置系数,大小是(out_channel)

# H_out = (H-kernel_size.[0]+2padding)/stride + 1 
input = t.autograd.Variable(t.randn(20, 16, 50, 100))   
m = nn.Conv2d(16, 33, 3, stride=2)   # 24 = (50-3)/2+1
output = m(input)
print(output.size())
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))  # 28 = (50-3+2*4)/2 + 1
output = m(input)
print(output.size())

#感受野(receptive field)和空洞卷积(dilated convolution)
#感受野计算: l(k) = l(k-1) + [(fk-1) * ∏Si]  {(i=1,2,...,k-1)  l(k)是第k层的感受野,fk是卷积核大小,si是步长,与padding无关}
# dilation-1 指的是kernel的间隔数量
#加入扩张卷积的filter为: filter = k + (k-1)*(dilation-1)   k是原卷积核大小
#一是可以理解为将卷积核扩展,如图卷积核为 3\times3 但是这里将卷积核变为 5\times5 即在卷积核每行每列中间加0。
#二是理解为在特征图上每隔1行或一列取数与 3\times3 卷积核进行卷积

# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
# 加入dilation的filter为7x5   3 + (3-1)*(3-1)= 7   #(50+2*4-7)/2 + 1 = 26

#引入dilation的原因:
# FCN中通过pooling增大感受野缩小图像尺寸,然后通过upsampling还原图像尺寸,但是这个过程中造成了精度的损失,
# 那么为了减小这种损失理所当然想到的是去掉pooling层,然而这样就导致特征图感受野太小,因此空洞卷积应运而生

output = m(input)
print(output.size())

#结果:
torch.Size([20, 33, 24, 49])
torch.Size([20, 33, 28, 100])
torch.Size([20, 33, 26, 100])

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值