pytorch-自我使用笔记

pytorch笔记

本文的目的

本文主要是由于自己使用pytorch一段时间了,但也是由于半路出家,赶鸭子上架,虽然复现了一些网络,但根基不稳,对于pytorch总是一知半解,模模糊糊,故再次做个学习笔记,记录下自己趟过的坑和一些在pytorch官方源码和各路大佬的源码中见到的使用技巧.(和一些看起来脱了裤子放屁的炫技(^ - ^))

PyTorch由4个主要包组成:

torch:类似于Numpy的通用数组库,可将张量类型转换torch.cuda.TensorFloat,并在GPU上进行计算。
torch.autograd:用于构建计算图形并自动获取梯度的包。
torch.nn:具有共享层和损失函数的神经网络库。
torch.optim:具有通用优化算法(如SGD,Adam等)的优化包

接口

torch.function
tensor.function
两者效果是一样的
如: torch.add(x,y) 等价于 x.add(y)

注意使用时修改变量:
x.add(y)->x数据不变,返回新的tensor
x.add_(y)->修改x数据,x=x+y (就是个原位操作嘛)

tensor是个啥

中文文档一般翻译叫张量…其实我的理解就是数组嘛,不过是为了自动求导和反向传播的需要,pytorch官方新定了个数据类型嘛,就是个类,不要太神话了,毕竟Python一切皆为类嘛,tensor类的属性,有

dtype:张量的数据类型,如torch.float,torch.double,用的最多的一般是 float32 和 int64(torch.long),类型官网解释torch.type
shape:张量的形状,如 (64, 3, 224, 224)
device:张量所在的设备,GPU/CPU,张量放在 GPU 上才能使用加速。官网torch.device,一般’cpu’,‘cuda’,多个显卡的指定哪块显卡"cuda:0".
data:被包装的 Tensor;
grad:data 的梯度;叶子节点的求导记录在这里
grad_fn:fn 表示 function 的意思,记录创建该张量时所用的方法(函数),比如说加法、乘法,这个操作在求导过程需要用到,记录这个方法主要「用于梯度的求导」。要不然怎么知道具体是啥运算?Tensor 的 Function,是自动求导的关键;非叶子节点的创建方法记录在grad_fn 中,叶子节点的grad_fn 为None.
requires_grad:指示是否需要梯度,有的不需要梯度;如果需要求导就设为true
is_leaf:指示是否是叶子节点(张量);一般是输入,权重,偏置. 因为损失函数对<权重>和<偏置>求导嘛,

tensor的常用操作

最普通的创建

torch.tensor(data=[1,1,1] , dtype=torch.float , device='cpu' , requires_grad=False , pin_memory=False)
torch.Tensor(data=[1,1,1] , dtype=torch.float , device='cpu' , requires_grad=False , pin_memory=False)

  1. 这里的 data,就是我们的数据,可以是 list,也可以是 numpy。
  2. dtype 这个是指明数据类型,默认与 data 的一致。
  3. device 是指明所在的设备,requires_grad 是是否需要梯度,在搭建神经网络的时候需要求导的那些参数这里要设置为 true。
  4. pin_memory 是否存于锁页内存,这个设置为 False 就可以。

补充:
torch.tensor,torch.Tensor,的区别:
1. 在后接list类型时,没区别,在接int类型时,torch.Tensor(2,3,4)括号里给的是维度shape,输出是维度为[2,3,4]的随机tensor.
2. torch.tensor(2)只能接一个给的是int ,输出是2,是个shape为[]的张量.
3. torch.Tensor生成的dtype是默认的FloatTensor
补充:
设置torch.tensor的默认类型,torch.set_default_tensor_type(torch.DoubleTensor)
5. torch.tensor是从数据中推断数据类型
6. torch.Tensor(*size)
- torch.Tensor(3,3) 随机 3*3
- torch.Tensor(3) 随机 1行3列
- torch.Tensor([3,3]) 指定[3,3]

创建新的Tensor

  1. torch.from_numpy(arr)从 numpy 创建 tensor」注意:这个创建的 Tensor 与原 ndarray 「共享内存」, 当修改其中一个数据的时候,另一个也会被改动。

  2. 依 size 创建全 0 ,全 1 的张量:torch.zeros,torch.ones在这里插入图片描述

    这些参数都比较好理解,layout 这个是内存中的布局形式,一般采用默认就可以。这个 out,表示输出张量,就是再把这个张量赋值给别的一个张量,但是这两个张量时一样的,指的同一个内存地址。

  3. torch.zeros_like,torch.ones_like()
    在这里插入图片描述
    这个是创建与 input 同形状的全 0 ,全 1,张量

  4. torch.full(), torch.full_like()
    在这里插入图片描述

    t = torch.full((3,3), 10)
    tensor([[10., 10., 10.],
           [10., 10., 10.],
           [10., 10., 10.]])
    
  5. torch.arange(start,end,step)[start,end)间隔step,创建等差的 1 维张量,数值区间 [start, end),注意这是右边开,取不到最后的那个数。在这里插入图片描述

    torch.arange(3,15,3)
    #tensor([ 3,  6,  9, 12])
    
  6. linspace(start,end,steps) 始到终,平均切成steps份(end)可以取得到, 创建均分的 1 维张量, 数值区间 [start, end] 注意这里都是闭区间,和上面的区分。并且这里的 steps 是数列的长度而不是步长。
    在这里插入图片描述

    torch.linspace(3,15,3)
    #tensor([ 3.,  9., 15.])
    
    t = torch.linspace(2, 10, 6)   
    # tensor([2, 3.6, 5.2, 6.8, 8.4, 10])
    # 这个步长是怎么算的?  (end-start) / (steps-1)
    
  7. torch.logspace(start,end,step)创建对数均分数列
    在这里插入图片描述
    base 表示以什么为底

  8. torch.eye(row,column) 单位tensor,指定行数列数,n,m 分别是矩阵的行数和列数。在这里插入图片描述

  9. 依概率分布创建张量torch.normal ( mean,std,size,out=None ):生成正态分布(高斯分布), 这个使用的比较多

    
    # 第一种模式 - 均值是标量, 方差是标量 - 此时产生的是一个分布, 从这一个分部中抽样相应的个数,所以这个必须指定size,也就是抽取多少个数
    t_normal = torch.normal(0, 1, size=(4,))
    print(t_normal)     # 来自同一个分布
    
    # 第二种模式 - 均值是标量, 方差是张量 - 此时会根据方差的形状大小,产生同样多个分布,每一个分布的均值都是那个标量
    std = torch.arange(1, 5, dtype=torch.float)
    print(std.dtype)
    t_normal2 = torch.normal(1, std)
    print(t_normal2)        # 也产生来四个数,但是这四个数分别来自四个不同的正态分布,这些分布均值相等
    
    # 第三种模式 - 均值是张量,方差是标量 - 此时也会根据均值的形状大小,产生同样多个方差相同的分布,从这几个分布中分别取一个值作为结果
    mean = torch.arange(1, 5, dtype=torch.float)
    t_normal3 = torch.normal(mean, 1)
    print(t_normal3)     # 来自不同的分布,但分布里面方差相等
    
    # 第四种模式 - 均值是张量, 方差是张量 - 此时需要均值的个数和方差的个数一样多,分别产生这么多个正太分布,从这里面抽取一个值
    mean = torch.arange(1, 5, dtype=torch.float)
    std = torch.arange(1, 5, dtype=torch.float)
    t_normal4 = torch.normal(mean, std)
    print(t_normal4)          # 来自不同的分布,各自有自己的均值和方差
    
  10. 标准正态分布:torch.randn(),torch.randn_like()
    在这里插入图片描述

  11. 生成均匀分布:torch.rand(),torch.rand_like() 在 [0,1) 生成均匀分布在这里插入图片描述

  12. torch.randint(), torch.randint_like():区间 [low,hight) 生成整数均匀分布
    在这里插入图片描述

  13. torch.randperm(n):生成从 0 - n-1 的随机排列, n 是张量的长度, 经常用来生成一个乱序索引。
    在这里插入图片描述

  14. torch.bernoulli(input):以 input 为概率,生成伯努利分布 (0-1 分布,两点分布), input:概率值在这里插入图片描述

修改Tensor形状

  1. torch1.size()=torch1.shape()

  2. torch1.dim

  3. torch1.numel(input) 返回Tensor元素个数

  4. torch1.view(*shape)返回对象与源Tensor共享内存 view(-1)打平

  5. torch1.reshape(*shape)返回新的Tensor,用于重塑张量,
    torch.reshape(input, shape):变换张量的形状,这个很常用,input 表示要变换的张量,shape表示新张量的形状。但注意,当张量在内存中是连续时,新张量与input共享数据内存

    # torch.reshape
    t = torch.randperm(8)       # randperm是随机排列的一个函数
    print(t)
    
    t_reshape = torch.reshape(t, (-1, 2, 2))    # -1的话就是根据后面那两个参数,计算出-1这个值,然后再转
    print("t:{}\nt_reshape:\n{}".format(t, t_reshape))
    
    t[0] = 1024
    print("t:{}\nt_reshape:\n{}".format(t, t_reshape))
    print("t.data 内存地址:{}".format(id(t.data)))
    print("t_reshape.data 内存地址:{}".format(id(t_reshape.data))) # 这个注意一下,两个是共内存的
    
    ## 结果:
    tensor([2, 4, 3, 1, 5, 6, 7, 0])
    t:tensor([2, 4, 3, 1, 5, 6, 7, 0])
    t_reshape:
    tensor([[[2, 4],
            [3, 1]],
    
           [[5, 6],
            [7, 0]]])
    t:tensor([1024,    4,    3,    1,    5,    6,    7,    0])
    t_reshape:
    tensor([[[1024,    4],
            [   3,    1]],
    
           [[   5,    6],
            [   7,    0]]])
    t.data 内存地址:1556953167336
    t_reshape.data 内存地址:1556953167336
    
  6. torch1.resize(*shape),和reshape一样,只是,shape多了的补零

  7. torch.transpose(input, dim0, dim1):交换张量的两个维度, 矩阵的转置常用, 在图像的预处理中常用, dim0 要交换的维度, dim1 表示要交换的问题

    # torch.transpose
    t = torch.rand((2, 3, 4))      # 产生0-1之间的随机数
    print(t)
    t_transpose = torch.transpose(t, dim0=0, dim1=2)    # c*h*w     h*w*c, 这表示第0维和第2维进行交换
    
  8. torch.t(input):2 维张量的转置, 对矩阵而言,相当于 torch.transpose(inpuot, 0,1)

  9. torch1.unsqueeze正左负右,指定维度插1

  10. torch.squeeze(input, dim=None, out=None):压缩长度为 1 的维度, dim 若为 None,移除所有长度为 1 的轴,若指定维度,当且仅当该轴长度为 1 时可以被移除

    # torch.squeeze
    t = torch.rand((1, 2, 3, 1))
    t_sq = torch.squeeze(t)
    t_0 = torch.squeeze(t, dim=0)
    t_1 = torch.squeeze(t, dim=1)
    print(t.shape)        # torch.Size([1, 2, 3, 1])
    print(t_sq.shape)     # torch.Size([2, 3])
    print(t_0.shape)     # torch.Size([2, 3, 1])
    print(t_1.shape)     # torch.Size([1, 2, 3, 1])```
    
    

12.torch1.item??? ?? ??? ??? ?? ??? ??? ???

索引操作

torch1.index_select(dim:int,index:tensor)
torch.index_select(input,dim:int,index:tensor)

  1. torch.index_select(input, dim, index, out=None)在维度 dim 上,按 index 索引数据,返回值,以 index 索引数据拼接的张量。
    t = torch.randint(0, 9, size=(3, 3))     #  从0-8随机产生数组成3*3的矩阵
    print(t)
    idx = torch.tensor([0, 2], dtype=torch.long)   # 这里的类型注意一下,要是long类型
    t_select = torch.index_select(t, dim=1, index=idx)  #第0列和第2列拼接返回
    print(t_select)
    
    	## 结果:
    tensor([[3, 7, 3],
         [4, 3, 7],
         [5, 8, 0]])
    tensor([[3, 3],
         [4, 7],
         [5, 0]])
       ```
    
  2. torch.masked_select(input, mask, out=None):按 mask 中的 True 进行索引,返回值:一维张量。input 表示要索引的张量,mask 表示与 input 同形状的布尔类型的张量。这种情况在选择符合某些特定条件的元素的时候非常好使,注意这个是返回一维的张量。
    mask = t.ge(5)   # le表示<=5, ge表示>=5 gt >5  lt <5
    print("mask:\n", mask)
    t_select1 = torch.masked_select(t, mask)   # 选出t中大于5的元素
    print(t_select1)
    
    ## 结果:
    mask:
    tensor([[False,  True, False],
         [False, False,  True],
         [ True,  True, False]])
    tensor([7, 7, 5, 8])
    
  3. 所以张量的索引,有两种方式:.index_select 和 .masked_select
    • index_select:按照索引查找 需要先指定一个 Tensor 的索引量,然后指定类型是 long 的
    • masked_select:就是按照值的条件进行查找,需要先指定条件作为 mask
      torch.nonzero(input)返回一个二维tensor,里面的每一个零维的元素是input非零元素的坐标
      torch.masked_select(input,mask)
      torch.gather(input,dim,index)1,index必须是LongTensor类型2,index啥样,输出就是啥样

比如
index=torch.LongTensor([[1,2,3],[4,5,2]])
torch.gather(x,0,index)

scatter_1,与gather的索引相同。

比如--------------把a按照索引,放到z的指定维度中
z.scatter_(1,index,a)

拼接操作(常用)

  1. torch.cat(tensors, dim=0, out=None):将张量按维度 dim 进行拼接, tensors 表示张量序列, dim 要拼接的维度
    	# 张量的拼接
    	t = torch.ones((2, 3))
    	print(t)
    	
    	t_0 = torch.cat([t, t], dim=0)       # 行拼接
    	t_1 = torch.cat([t, t], dim=1)    # 列拼接
    	print(t_0, t_0.shape)
    	print(t_1, t_1.shape)
    	
    	# 结果:
    	tensor([[1., 1., 1.],
    	     [1., 1., 1.]])
    	tensor([[1., 1., 1.],
    	     [1., 1., 1.],
    	     [1., 1., 1.],
    	     [1., 1., 1.]]) torch.Size([4, 3])
    	tensor([[1., 1., 1., 1., 1., 1.],
    	     [1., 1., 1., 1., 1., 1.]]) torch.Size([2, 6])
    
    .cat 是在原来的基础上根据行和列,进行拼接.
    注意:torch.cat([这里面的tensor不能是0维的])
    比如:
    a=torch.tensor(1)
    b=torch.tensor(2)
    torch.cat([a,b]) # 凉凉,会报错
    
    a=torch.tensor([1])
    b=torch.tensor([2])
    torch.cat([a,b]) # 这样就好了
    

2.torch.stack(tensors, dim=0, out=None):在新创建的维度 dim 上进行拼接, tensors 表示张量序列, dim 要拼接的维度

	t_stack = torch.stack([t,t,t], dim=0)
	print(t_stack)
	print(t_stack.shape)
	
	t_stack1 = torch.stack([t, t, t], dim=1)
	print(t_stack1)
	print(t_stack1.shape)
	
	## 结果:
	tensor([[[1., 1., 1.],
	        [1., 1., 1.]],
	
	       [[1., 1., 1.],
	        [1., 1., 1.]],
	
	       [[1., 1., 1.],
	        [1., 1., 1.]]])
	torch.Size([3, 2, 3])
	tensor([[[1., 1., 1.],
	        [1., 1., 1.],
	        [1., 1., 1.]],
	
	       [[1., 1., 1.],
	        [1., 1., 1.],
	        [1., 1., 1.]]])
	torch.Size([2, 3, 3])
.stack 是根据给定的维度新增了一个新的维度,在这个新维度上进行拼接,这里会新增一个维度,在新增维度上进行拼接,
直接点来看,dim=3,拼接后其对应的维度就是3.

分割

  1. 张量的切分 torch.chunk(input, chunks, dim=0):将张量按维度 dim 进行平均切分,返回值是张量列表,注意,如果不能整除, 最后一份张量小于其他张量。chunks 代表要切分的维度。下面看一下代码实现:
a = torch.ones((2, 7))  # 7
list_of_tensors = torch.chunk(a, dim=1, chunks=3)   # 第一个维度切成三块, 那么应该是(2,3), (2,3), (2,1)  因为7不能整除3,所以每一份应该向上取整,最后不够的有多少算多少
print(list_of_tensors)
for idx, t in enumerate(list_of_tensors):
   print("第{}个张量:{}, shape is {}".format(idx+1, t, t.shape))

## 结果:
(tensor([[1., 1., 1.],
       [1., 1., 1.]]), tensor([[1., 1., 1.],
       [1., 1., 1.]]), tensor([[1.],
       [1.]]))1个张量:tensor([[1., 1., 1.],
       [1., 1., 1.]]), shape is torch.Size([2, 3])2个张量:tensor([[1., 1., 1.],
       [1., 1., 1.]]), shape is torch.Size([2, 3])3个张量:tensor([[1.],
       [1.]]), shape is torch.Size([2, 1])
  1. torch.split(tensor, split_size_or_sections, dim=0):这个也是将张量按维度 dim 切分,但是这个更加强大,可以指定切分的长度,split_size_or_sections 为 int 时表示每一份的长度, 为 list 时,按 list 元素切分
# split
t = torch.ones((2, 5))

list_of_tensors = torch.split(t, [2, 1, 2], dim=1)  # [2 , 1, 2], 这个要保证这个list的大小正好是那个维度的总大小,这样才能切
for idx, t in enumerate(list_of_tensors):
   print("第{}个张量:{}, shape is {}".format(idx+1, t, t.shape))

## 结果1个张量:tensor([[1., 1.],
       [1., 1.]]), shape is torch.Size([2, 2])2个张量:tensor([[1.],
       [1.]]), shape is torch.Size([2, 1])3个张量:tensor([[1., 1.],
       [1., 1.]]), shape is torch.Size([2, 2])

张量的数学运算

torch.add(input, alpha=2, other=10)
逐元素计算input+alpha * other

torch.sub()
torch.pow()
torch.div()
torch.mul()

torch.addcdiv(input, value=1, tensor1, tensor2, out=None)
o u t i = i n p u t i + v a l u e × t e n s o r 1 i t e n s o r 2 i out_i=input_i+value \times\frac{tensor1_i}{tensor2_i} outi=inputi+value×tensor2itensor1i
torch.addcmul(input, value=1, tensor1, tensor2, out=None)
o u t i = i n p u t i + v a l u e × t e n s o r 1 i × t e n s o r 2 i out_i=input_i+value \times tensor1_i \times tensor2_i outi=inputi+value×tensor1i×tensor2i

torch.log(input, out=None)
torch.log10(input, out=None)
torch.log2(input, out=None)
torch.exp(input, out=None)

torch.abs(input, out=None)
torch.acos(input, out=None)

torch.cosh(input, out=None)
torch.cos(input, out=None)
torch.asin(input, out=None)
torch.atan(input, out=None)
torch.atan2(input, out=None)

其他操作

  1. 将Tensor张量转化为numpy矩阵

sub_np1 = sub_ts.numpy() #sub_ts为tensor张量

pytorch的自动求导机制

在这里插入图片描述

torch.autograd.backward

  1. tensor的is_leaf属性,即叶子节点这个属性.叶子节点:用户创建的节点, 比如上面的 x 和 w。叶子节点是非常关键的,在上面的正向计算和反向计算中,其实都是依赖于我们叶子节点进行计算的。is_leaf: 指示张量是否是叶子节点。

  2. 为什么要设置叶子节点的这个概念的?主要是为了节省内存,因为我们在反向传播完了之后,非叶子节点的梯度是默认被释放掉的。也就是图中的y节点,要想不释放y节点的梯度,则需要在backword之前加一行:y.retain_grad()

  3. grad_fn属性:记录创建该张量时所用的方法(函数),记录这个方法主要「用于梯度的求导」。要不然怎么知道具体是啥运算?

  4. grad属性:记录叶子节点的梯度,非叶子节点的grad属性为None

  5. Pytorch 自动求导机制使用的是 torch.autograd.backward 方法,功能就是自动求取梯度。在这里插入图片描述

    • tensors 表示用于求导的张量,如 loss。
    • retain_graph 表示保存计算图, 由于 Pytorch 采用了动态图机制,在每一次反向传播结束之后,计算图都会被释放掉。如果我们不想被释放,就要设置这个参数为 True.
      #这样会报错
      y.backword()
      y.backword()	# 上一个backword之后计算图就没有了,所以这里会报错
      #这样就好了
      y.backword(retain_graph=true)
      y.backword()
      
    • create_graph 表示创建导数计算图,用于高阶求导。
    • grad_tensors 表示多梯度权重。如果有多个 loss 需要计算梯度的时候,就要设置这些 loss 的权重比例。(就是backward的gradient)
      import torch
      w = torch.tensor([1.], requires_grad=True)
      b = torch.tensor([2.], requires_grad=True)
      x = torch.tensor([3.], requires_grad=True)
      
      y = torch.mul(x, w)
      z1 = torch.add(y, b)
      z2 = torch.mul(y, b)
      
      loss = torch.cat([z1, z2], dim=0)  # 这里torch.cat([这里面的tensor不能是0维的])
      # loss.backward()	# 这里
      loss.backward(gradient=torch.tensor([1., 2.]))
      
      print(w.grad)
      print(x.grad)
      #输出
      tensor([9.])
      tensor([3.])
      
    • 调用backword的时候会自动调用torch.autograd.backward
      def backward(self, gradient=None, retain_graph=None, create_graph=False):
      		torch.autograd.backward(self, gradient, retain_graph, create_graph)
      
      在这里插入图片描述

torch.autograd.grad

在这里插入图片描述

  • outputs: 用于求导的张量,如 loss
  • inputs: 需要梯度的张量,如上面例子的 w
  • create_graph: 创建导数计算图,用于高阶求导
  • retain_graph: 保存计算图
  • grad_outputs: 多梯度权重
  • 例子:
    x = torch.tensor([3.], requires_grad=True)
    y = torch.pow(x, 2)   # y=x^2
    
    # 一次求导
    grad_1 = torch.autograd.grad(y, x, create_graph=True)   # 这里必须创建导数的计算图, grad_1 = dy/dx = 2x
    print(grad_1)   # (tensor([6.], grad_fn=<MulBackward0>),) 这是个元组,二次求导的时候我们需要第一部分
    
    # 二次求导
    grad_2 = torch.autograd.grad(grad_1[0], x)    # grad_2 = d(dy/dx) /dx = 2
    print(grad_2)  # (tensor([2.]),)
    

这个函数还允许对多个自变量求导数:

	x1 = torch.tensor(1.0,requires_grad = True) # x需要被求导
	x2 = torch.tensor(2.0,requires_grad = True)
	
	y1 = x1*x2
	y2 = x1+x2
	
	
	# 允许同时对多个自变量求导数
	(dy1_dx1,dy1_dx2) = torch.autograd.grad(outputs=y1,inputs = [x1,x2],retain_graph = True)
	print(dy1_dx1,dy1_dx2)        # tensor(2.) tensor(1.)
	
	# 如果有多个因变量,相当于把多个因变量的梯度结果求和
	(dy12_dx1,dy12_dx2) = torch.autograd.grad(outputs=[y1,y2],inputs = [x1,x2])
	print(dy12_dx1,dy12_dx2)        # tensor(3.) tensor(2.)

关于 Pytorch 的自动求导系统要注意:

  1. 梯度不自动清零:就是每一次反向传播,梯度都会叠加上去,这个要注意,举个例子:
    w = torch.tensor([1.], requires_grad=True)
    x = torch.tensor([2.], requires_grad=True)
    
    for i in range(4):
        a = torch.add(w, x)
        b = torch.add(w, 1)
        y = torch.mul(a, b)
    
        y.backward()
        #w.grad.zero_()  # 反向传播前,记得!!手动清除梯度,否则会累加
        print(w.grad)
    
    ## 结果:
    tensor([5.])          
    tensor([10.])
    tensor([15.])
    tensor([20.])
    
  2. 叶子节点不能原位操作,也就是
  3. 依赖于叶子节点的节点默认是求梯度

数据处理

torch.utils.data

torch.utils.data工具包包括4个类,分别是Dataset,DataLoader,random_split

torch.utils.data
Dataset
DataLoader
random_split
  • Dataset :是一个抽象类, 所有自定义的 Dataset 都需要继承这个类,并且必须复写 __getitem__(),__len__ 这两个类方法。

    __getitem__ 方法的是 Dataset 的核心,作用是接收一个索引,返回一个样本这,里给出我一般的重写方法.

    import torch.utils.data as data
    import os
    import PIL.Image as Image
    import torchvision.transforms as transforms
    import model
    import torch
    
    classes = {'dandelion': 1, 'sunflowers': 2, 'daisy': 3, 'roses': 4, 'tulips': 5}
    
    
    class My_dataset(data.Dataset):
        def __init__(self, data_dir, transform):
            super().__init__()
            self.data_info = self.get_img_info(data_dir)  # data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
            self.transform = transform
    
        def __getitem__(self, index):
            path_img, label = self.data_info[index]
            img = Image.open(path_img).convert('RGB')  # 0~255
    
            if self.transform is not None:
                img = self.transform(img)  # 在这里做transform,转为tensor等等
            return img, label
    
        def __len__(self):
            return len(self.data_info)
    
        def get_img_info(self, data_dir):
            data_info = list()
            for root, dirs, _ in os.walk(data_dir):
                # 遍历类别
                for sub_dir in dirs:
                    img_names = os.listdir(os.path.join(root, sub_dir))
                    img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))
                    # 遍历图片
                    for i in range(len(img_names)):
                        img_name = img_names[i]
                        path_img = os.path.join(root, sub_dir, img_name)
                        label = classes[sub_dir]
                        data_info.append((path_img, int(label)))
            return data_info
    
  1. DataLoader:定义一个新的迭代器,实现将dataset返回的图像和信息进行打包batch,顺带还能打乱,加速读取等等.

    data. DataLoader (
    dataset,
    batch_ size=1,
    shuffle=False,
    sampler=None,
    batch_ s ampler=None,
    num_ workers=0,
    collate_ fn=<function default collate at 0x7f108ee01 620>, pin_ memory=False,
    drop_ last=False,
    timeout=0 ,
    worker init fn=None,
    )
    主要参数说明:
    dataset:加载的数据集。
    batch size:批大小。
    shuffle:是否将数据打乱。
    sampler: 样本抽样。
    num_ workers: 使用多进程加载的进程数,0代表不使用多进程。
    collate fn:如何将多个样本数据拼接成-一个 batch,一般使用默认的拼接方式即可。
    pin_ memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快一-些。
    drop_last: dataset 中的数据个数可能不是batch size 的整数倍,drop_ last 为True会将多出来不足-一个batch的数据丢弃

torchvision

在torchvision计算机视觉工具包中,我们在安装Pytorch的时候顺便安装了这个torchvision(可以看看上面的搭建环境)。在torchvision中,有三个主要的模块:

torchvision
transforms
datasets
models
  • torchvision.transforms: 常用的图像预处理方法, 比如标准化,中心化,旋转,翻转等操作
  • trochvision.datasets: 常用的数据集的dataset实现, MNIST, CIFAR-10, ImageNet等
  • torchvision.models: 常用的模型预训练, AlexNet, VGG, ResNet, GoogLeNet等。

补充: 这里的transforms方法最为常用,datasets在图像分类问题中可以直接用,但在其他任务中就不行了,其实现依旧是按照目录分类数据集.

transforms 的方法

transforms.Compose方法是将一系列的transforms方法进行有序的组合包装,具体实现的时候,依次的用包装的方法对图像进行操作。

transform1 = transforms.Compose([
    transforms.ToTensor(),
])

我们来看一下Compose的源码:在这里插入图片描述
可以看到Compose里面放一个列表,列表里面都是类对象(并不是函数),如下:

在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

  1. 其实这些类对象都是:类函数对象,在__call__方法中实现了功能
  2. Compose的实现原理就是,将输入图像传入第一个方法,将输出结果传入下一个方法,然后循环往复,注意最后传ToTensor方法.
  3. 可以看出如果要写一个自己的transforms方法,就按照格式
    class ToTensor(object):
    def __init__(self, ...):
    	...
    	
    def __call__(self, ...):
     	...
        return img
        
    def __repr__(self):
        return self.__class__.__name__ + '()'
    

transforms.ToTensor方法是将图像转换成张量,同时会进行归一化的一个操作,将张量的值从0-255转到0-1,且自动交换了dim:0和dim:2
transforms.Normalize方法是将数据进行标准化

transforms.Resize方法改变图像大小,只能接受PIL格式的图片
transforms.RandomCrop方法对图像进行裁剪(这个在训练集里面用,验证集就用不到了)

图像裁剪
transforms.CenterCrop(size): 图像中心裁剪图片, size是所需裁剪的图片尺寸,如果比原始图像大了, 会默认填充0。
transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant): 从图片中位置随机裁剪出尺寸为size的图片, size是尺寸大小,padding设置填充大小(当为a, 上下左右均填充a个像素, 当为(a,b), 上下填充b个,左右填充a个,当为(a,b,c,d), 左,上,右,下分别填充a,b,c,d个), pad_if_need: 若图像小于设定的size, 则填充。padding_mode表示填充模型, 有4种,constant像素值由fill设定, edge像素值由图像边缘像素设定,reflect镜像填充, symmetric也是镜像填充, 这俩镜像是怎么做的看官方文档吧。镜像操作就类似于复制图片的一部分进行填充。
transforms.RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(3/4, 4/3), interpolation): 随机大小,长宽比裁剪图片。scale表示随机裁剪面积比例,ratio随机长宽比, interpolation表示插值方法。
FiveCrop, TenCrop: 在图像的上下左右及中心裁剪出尺寸为size的5张图片,后者还在这5张图片的基础上再水平或者垂直镜像得到10张图片,具体使用这里就不整理了。

图像的翻转和旋转
RandomHorizontalFlip(p=0.5), RandomVerticalFlip(p=0.5): 依概率水平或者垂直翻转图片, p表示翻转概率
RandomRotation(degrees, resample=False, expand=False, center=None):随机旋转图片, degrees表示旋转角度 , resample表示重采样方法, expand表示是否扩大图片,以保持原图信息。

图像变换
transforms.Pad(padding, fill=0, padding_mode='constant'): 对图片边缘进行填充
transforms.ColorJitter(brightness=0, contrast=0, saturation=0, hue=0):调整亮度、对比度、饱和度和色相, 这个是比较实用的方法, brightness是亮度调节因子, contrast对比度参数, saturation饱和度参数, hue是色相因子。
transfor.RandomGrayscale(num_output_channels, p=0.1): 依概率将图片转换为灰度图, 第一个参数是通道数, 只能1或3, p是概率值,转换为灰度图像的概率
transforms.RandomAffine(degrees, translate=None, scale=None, shear=None, resample=False, fillcolor=0): 对图像进行仿射变换, 反射变换是二维的线性变换, 由五中基本原子变换构成,分别是旋转,平移,缩放,错切和翻转。degrees表示旋转角度, translate表示平移区间设置,scale表示缩放比例,fill_color填充颜色设置, shear表示错切
transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False): 这个也比较实用, 对图像进行随机遮挡, p概率值,scale遮挡区域的面积, ratio遮挡区域长宽比。随机遮挡有利于模型识别被遮挡的图片。value遮挡像素。「这个是对张量进行操作,所以需要先转成张量才能做」
transforms.Lambda(lambd): 用户自定义的lambda方法, lambd是一个匿名函数。lambda [arg1 [, arg2…argn]]: expression
.Resize, .ToTensor, .Normalize: 这三个方法上面具体说过,在这里只是提一下子。

对几个transforms的操作进行选择,使得图像预处理更加的灵活。
transforms.RandomChoice([transforms1, transforms2, transforms3]): 从一系列transforms方法中随机选一个
transforms.RandomApply([transforms1, transforms2, transforms3], p=0.5): 依据概率执行一组transforms操作
transforms.RandomOrder([transforms1, transforms2, transforms3]): 对一组transforms操作打乱顺序

torch.nn.Module

torch.nn
nn.functional
张量子类,表示可学习参数,如 weight,bias
nn. Module
所有网络层基类,管理网络属性
nn.init
函数具体实现,如卷积,池化,激活函数等
nn.Parameter
参数数始化方法
  • torch.nn: 这是 Pytorch 的神经网络模块,这里的 Module 就是它的子模块之一,另外还有几个与 Module 并列的子模块,这些子模块协同工作,各司其职。

  • nn.Module 这个类中,有 8 个重要的属性,用于管理整个模型,他们都是以有序字典的形式存在着:

    1 self. parameters= OrderedDict()
    2 self. buffers OrderedDict()
    3 self. backward_ hooks= OrderedDict()
    4 self. forward_ hooks= OrderedDict()
    5 self. forward_pre_ hooks= OrderedDict()
    6 self. state_dict_ hooks= OrderedDict()
    7 self. load state dict pre hooks OrderedDict()
    8 self. modules= OrderedDict()

    • _parameters:存储管理属于 nn.Parameter 类的属性,例如权值,偏置这些参数
    • _modules: 存储管理 nn.Module 类, 比如神经网络中的,卷积层,池化层,就会存储在 modules 中(且卷积层等等也都是继承了nn.Modules类,也都含有那8个属性)
    • _buffers: 存储管理缓冲属性,如 BN 层中的 running_mean,std 等都会存在这里面
    • ***_hooks: 存储管理钩子函数(5 个与 hooks 有关的字典,这个先不用管)
  • Module类的使用的方法定义在了 forward 函数中。(后面的卷积层,线性层,损失函数等都是Module类,其都有上面的8个属性,且方法都定义在forward中.

搭建网络的三种API

Containers
nn.Sequetial
按顺序包装多个网络层
nn.ModuleList
像 python的list一样包装多个网络层
nn. ModuleDict
像python的dict一样包装多个网络层

nn.Sequential(这个最常用)

nn.Sequential这是 nn.module 的容器,用于「按顺序」包装一组网络层。

  • 常规用法:
	 class LeNetSequential(nn.Module):
	     def __init__(self, classes):
	         super(LeNetSequential, self).__init__()
	         self.features = nn.Sequential(
	             nn.Conv2d(3, 6, 5),
	             nn.ReLU(),
	             nn.MaxPool2d(kernel_size=2, stride=2),
	             nn.Conv2d(6, 16, 5),
	             nn.ReLU(),
	             nn.MaxPool2d(kernel_size=2, stride=2),)
	 
	         self.classifier = nn.Sequential(
	             nn.Linear(16*5*5, 120),
	             nn.ReLU(),
	             nn.Linear(120, 84),
	             nn.ReLU(),
	             nn.Linear(84, classes),)
	 
	     def forward(self, x):
	         x = self.features(x)
	         x = x.view(x.size()[0], -1)
	         x = self.classifier(x)
	         return x
  • 利用torch.nn.SequentialOrderedDict实现构造网络,这样网络的每一层都是自己命名(这个有序字典的方法是写在nn.Sequential源码中的
from collections import OrderedDict

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.backbone = torch.nn.Sequential(
            OrderedDict([
                ("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1)),
                ("relu1", torch.nn.ReLU()),
                ("pool", torch.nn.MaxPool2d(2))
            ]))

        self.dense = torch.nn.Sequential(
            OrderedDict([
                ("dense1", torch.nn.Linear(32 * 3 * 3, 128)),
                ("relu2", torch.nn.ReLU()),
                ("dense2", torch.nn.Linear(128, 10))
            ]))

nn.ModuleList

nn.ModuleList 是 nn.module 的容器,用于包装一组网络层,以「迭代」方式调用网络层,主要方法:

  • append(): 在 ModuleList 后面添加网络层
  • extend(): 拼接两个 ModuleList
  • insert(): 指定在 ModuleList 中位置插入网络层
class ModuleList(nn.Module):
    def __init__(self):
        super(ModuleList, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])

    def forward(self, x):
        for i, linear in enumerate(self.linears):
            x = linear(x)
        return x

nn.ModuleDict

nn.ModuleDict 是 nn.module 的容器,用于包装一组网络层,以「索引」方式调用网络层,主要方法:

  • clear(): 清空 ModuleDict
  • items(): 返回可迭代的键值对(key-value pairs)
  • keys(): 返回字典的键(key)
  • values(): 返回字典的值(value)
  • pop(): 返回一对键值对,并从字典中删除
class ModuleDict(nn.Module):
    def __init__(self):
        super(ModuleDict, self).__init__()
        self.choices = nn.ModuleDict({
            'conv': nn.Conv2d(10, 10, 3),
            'pool': nn.MaxPool2d(3)
        })

        self.activations = nn.ModuleDict({
            'relu': nn.ReLU(),
            'prelu': nn.PReLU()
        })

    def forward(self, x, choice, act):
        x = self.choices[choice](x)
        x = self.activations[act](x)
        return x
    
net = ModuleDict()
fake_img = torch.randn((4, 10, 32, 32))
output = net(fake_img, 'conv', 'relu')    # 在这里可以选择我们的层进行组合
print(output)

前面通过self.choices这个 ModuleDict 可以选择卷积或者池化,而下面通过self.activations这个 ModuleDict 可以选取是用哪个激活函数

网络的各种层

卷积层

  1. nn.Conv2d: 二维卷积在这里插入图片描述

主要参数:

  • in_channels: 输入通道数
  • out_channels: 输出通道数, 等价于卷积核个数
  • kernel_size: 卷积核尺寸, 这个代表着卷积核的大小
  • stride: 步长, 这个指的卷积核滑动的时候,每一次滑动几个像素。
  • padding: 填充个数,通常用来保持输入和输出图像的一个尺寸的匹配,
  • dilation: 孔洞卷积大小:孔洞卷积就可以理解成一个带孔的卷积核,常用于图像分割任务,主要功能就是提高感受野。也就是输出图像的一个参数,能看到前面图像更大的一个区域。
  • bias: 偏置
  1. nn.ConvTranspose2d: 转置卷积,实现卷积上采样
    在这里插入图片描述

池化层

  1. nn.MaxPool2d: 对二维信号(图像)进行最大值池化。
    在这里插入图片描述
  • kernel_size: 池化核尺寸
  • stride: 步长
  • padding: 填充个数
  • dilation: 池化核间隔大小
  • ceil_mode: 尺寸向上取整
  • return_indices: 记录池化像素索引
  1. nn.AvgPool2d: 对二维信号(图像)进行平均值池化
    在这里插入图片描述
  • count_include_pad: 填充值用于计算
  • divisor_override: 除法因子, 这个是求平均的时候那个分母,默认是有几个数相加就除以几,当然也可以自己通过这个参数设定
  1. nn.MaxUnpool2d: 这个的功能是对二维信号(图像)进行最大池化上采样,反池化操作
    在这里插入图片描述

全连接层

nn.Linear(in_features, out_features, bias=True)

  • in_features: 输入节点数
  • out_features: 输出节点数
  • bias: 是否需要偏置
    使用方法如下:
    max_pool = torch.nn.MaxPool2d((2, 2), (2, 2), return_indices=True)  # 这里设为True记录下最大值在矩阵中的索引
    img_pool, indices = max_pool(img_tensor)
    
    maxup_pool = torch.nn.MaxUnpool2d((2, 2), stride=(2, 2))
    img_unpool = maxup_pool(img_pool, indices)  # 反池化要把索引加上
    

初始化

因为model继承于nn.Module类,其属性modules记录这网络各个层的信息

modules下的各个层的weight.data属性记录这这一层的权重,bias.data记录这偏置

下面这个例子详细展示了初始化的方法:


for layer in net.modules():  # 遍历每一层
    if isinstance(layer, torch.nn.Conv2d):
        torch.nn.init.xavier_normal_(layer.weights.data)
        if layer.bias != None:
            layer.bias.data.zero_()
    if isinstance(layer, torch.nn.BatchNorm2d):
        layer.weight.data.fill_(1)  # 原地初始化为1
        layer.bias.data.zero_()
    if isinstance(layer, torch.nn.Linear):
        torch.nn.init.normal(layer.weight.data, 0, 0.01)
        layer.bias.data.zero_()

torch.nn.init 中提供了常用的初始化方法函数

Xavier 初始化方法
  1. 论文在《Understanding the difficulty of training deep feedforward neural networks》

公式推导是从“方差一致性”出发,初始化的分布有均匀分布和正态分布两种

Xavier权重初始化,有利于缓解带有sigmoid,tanh的这样的饱和激活函数的神经网络的梯度消失和爆炸现象。

Xavier 均匀分布:

torch.nn.init.xavier_uniform_(tensor, gain=1)
这里有一个 gain,增益的大小是依据激活函数类型来设定

  for m in self.modules():
    if isinstance(m, nn.Linear):
      # Xavier初始化权重
      tanh_gain = nn.init.calculate_gain('tanh')
      nn.init.xavier_uniform_(m.weight.data, gain=tanh_gain)

这里面用到了一个函数nn.init.calculate_gain(nonlinearity, param=None)这个函数的作用是计算激活函数的,方差变化尺度,怎么理解这个方差变化尺度呢?
其实就是输入数据的方差除以经过激活函数之后的输出数据的方差。nonlinearity 表示激活函数的名称,如tanhparam 表示激活函数的参数,如 Leaky ReLUnegative_slop

Xavier 正态分布:

torch.nn.init.xavier_normal_(tensor, gain=1)

kaiming 初始化方法

论文在《 Delving deep into rectifiers: Surpassing human-levelperformance on ImageNet classification 》,公式推导同样从“方差一致性”出法

kaiming是针对 xavier 初始化方法在 relu 这一类激活函数表现不佳而提出的改进,详细可以参看论文。

这个依然是考虑的方差一致性原则,针对的激活函数是 ReLU 及其变种

kaiming 均匀分布

torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

  1. a 为激活函数的负半轴的斜率,relu 是 0
  2. mode- 可选为 fan_in 或 fan_out, fan_in 使正向传播时,方差一致; fan_out 使反向传播时,方差一致
  3. nonlinearity- 可选 relu 和 leaky_relu ,默认值为 。 leaky_relu
  for m in self.modules():
   if isinstance(m, nn.Linear):
     nn.init.kaiming_normal_(m.weight.data)
     # nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))     # 这两句话其实作用一样,不过自己写还得计算出标准差
kaiming 正态分布

torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu')

均匀分布初始化

torch.nn.init.uniform_(tensor, a=0, b=1)

使值服从均匀分布 U(a,b)

正态分布初始化

torch.nn.init.normal_(tensor, mean=0, std=1)

使值服从正态分布 N(mean, std),默认值为 0,1

常数初始化

torch.nn.init.constant_(tensor, val)

使值为常数 val nn.init.constant_(w, 0.3)

单位矩阵初始化

torch.nn.init.eye_(tensor)

将二维 tensor 初始化为单位矩阵(the identity matrix)

正交初始化

torch.nn.init.orthogonal_(tensor, gain=1)

使得 tensor 是正交的,论文: Exact solutions to the nonlinear dynamics of learning in deep linear neural networks” - Saxe, A. et al. (2013)

损失函数(官方目前提供了17个/(ㄒoㄒ)/~~看得我想哭!!)

1. nn.CrossEntropyLoss

将输入经过 softmax 激活函数之后,再计算其与 target 的交叉熵损失。

即该方法将nn.LogSoftmax()nn.NLLLoss()进行了结合。严格意义上的交叉熵损失函数应该是nn.NLLLoss()
注意:多分类任务如果损失函数选择了这个,那千万不要在最后加softmax函数了,损失函数里已经有了。

计算公式:在这里插入图片描述
API:
在这里插入图片描述
weight:各类别的 loss 设置权值,weight 必须是 float类型的 tensor,其长度要于类别数一致,即每一个类别都要设置有 weight

ignore_index:忽略某个类别

reduction:计算模式,可为 none/sum/mean,none 表示逐个元素计算,这样有多少个样本就会返回多少个 loss。sum 表示所有元素的 loss 求和,返回标量,mean 所有元素的 loss 求加权平均

reduce(bool): 返回值是否为标量,默认为 True。

size_average(bool): 当 reduce=True 时有效。为 True 时,返回的 loss 为除以权重之和的平均
值;为 False 时,返回的各样本的 loss 之和。

import torch
import torch.nn as nn
# fake data
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)  # 这里就是模型预测的输出, 这里是两个类,可以看到模型输出是数值,我们得softmax一下转成分布
target = torch.tensor([0, 1, 1], dtype=torch.long)  # 这里的类型必须是long, 两个类0和1

# 三种模式的损失函数
loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')
loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')
loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')

weight = torch.tensor([1, 2], dtype=torch.float32)
loss_f_none_weight = nn.CrossEntropyLoss(weight=weight, reduction='none')

# forward
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
loss_none_weight = loss_f_none_weight(inputs, target)
# view
print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)
print("weight_loss", loss_none_weight)

##结果
Cross Entropy Loss:
		tensor([1.3133, 0.1269, 0.1269]) tensor(1.5671) tensor(0.5224)
weight_loss tensor([1.3133, 0.2539, 0.2539])

补充:mean 模式下有加权的话,求平均不是除以样本的个数,而是样本所占的权值的总份数

2. nn.BCELoss

这个是交叉熵损失函数的特例,二分类交叉熵。注意:输入值取值在 [0,1]

class torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='element wise_mean')

计算公式:
在这里插入图片描述
!!注意 !!注意
对比一下和CrossEntropyLoss的区别:

这里的区别在fastercnn论文和pytorch管方复现的源码有所体现
CrossEntropyLoss

inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)  # 这里就是模型预测的输出, 这里是两个类,可以看到模型输出是数值,我们得softmax一下转成分布
target = torch.tensor([0, 1, 1], dtype=torch.long)  # 这里的类型必须是long, 两个类0和1

BCELoss

import torch
import torch.nn as nn
# fake data
inputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float)  # 这里就是模型预测的输出,这里是两个类,可以看到模型输出是数值,我们得softmax一下转成分布
target = torch.tensor([[0, 1], [0, 1], [1, 0]], dtype=torch.float)  # 这里的类型变了,注意格式,

inputs = torch.sigmoid(inputs)  # 用sigmoid将输入转化到0-1之间(!!!必须0-1之间)

# 三种模式的损失函数
loss_f_none = nn.BCELoss(weight=None, reduction='none')
loss_f_sum = nn.BCELoss(weight=None, reduction='sum')
loss_f_mean = nn.BCELoss(weight=None, reduction='mean')

weight = torch.tensor([1, 2], dtype=torch.float)
loss_f_none_weight = nn.BCELoss(weight=weight, reduction='none')

# forward
loss_none = loss_f_none(inputs, target)
loss_sum = loss_f_sum(inputs, target)
loss_mean = loss_f_mean(inputs, target)
loss_none_weight = loss_f_none_weight(inputs, target)
# view
print("Cross Entropy Loss:\n ", loss_none, loss_sum, loss_mean)
print("weight_loss", loss_none_weight)
# ## 结果:
# Cross Entropy Loss:
#   tensor([[1.3133, 0.1269],
#         [1.3133, 0.0486],
#         [0.3133, 3.0486]]) tensor(6.1639) tensor(1.0273)
# weight_loss tensor([[1.3133, 0.2539],
#         [1.3133, 0.0972],
#         [0.3133, 6.0972]])

看到了吧,CrossEntropyLoss只接受正确的target就行了,其loss也是只算正确的那一支预测和目标的差距。

BCELoss就不一样了,它正确和错误的都要算,也就是每个神经元都要去算loss。

3. nn.BCEWithLogitsLoss

class torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='elementwise_mean', pos_weight=None)
计算公式:在这里插入图片描述
这里了就是加了个 sigmoid。

功能:
将 Sigmoid 与 BCELoss 结合,类似于 CrossEntropyLoss(将 nn.LogSoftmax()nn.NLLLoss()进行结合)。即 input 会经过 Sigmoid 激活函数,将 input 变成概率分布的形式。

参数:
weight(Tensor) : 为 batch 中单个样本设置权值,If given, has to be a Tensor of size “nbatch”.
pos_weight: 正样本的权重, 这个是平衡正负样本的权值用的, 对正样本进行一个权值设定。比如我们正样本有 100 个,负样本有 300 个,那么这个数可以设置为 3,在类别不平衡的时候可以用。
当 p>1,提高召回率,当 P<1,提高精确度。可达到权衡召回率(Recall)和精确度(Precision)的作用。Must be a vector with length equal to the number of classes.
size_average(bool) 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool) 返回值是否为标量,默认为 True

4. nn.NLLoss

在这里插入图片描述
实现功能就是下面这张图:
在这里插入图片描述
参数:
weight(Tensor)-:为每个类别的 loss 设置权值,常用于类别不均衡问题。weight 必须是 float类型的 tensor,其长度要于类别 C 一致,即每一个类别都要设置有 weight。
size_average(bool):当 reduce=True 时有效。为 True 时,返回的 loss 为除以权重之和的平均
值;为 False 时,返回的各样本的 loss 之和。
reduce(bool):返回值是否为标量,默认为 True。
ignore_index(int):忽略某一类别,不计算其 loss,其 loss 会为 0,并且,在采用size_average 时,不会计算那一类的 loss,除的时候的分母也不会统计那一类的样本。
注意

当带上权值,reduce = True, size_average = True, 其计算公式为:
在这里插入图片描述

例如当 input 为[[0.6, 0.2, 0.2], [0.4, 1.2, 0.4]],target= [0, 1], weight = [0.6, 0.2, 0.2]
l1 = - 0.6*0.6 = - 0.36
l2 = - 1.2*0.2 = - 0.24
loss = -0.36/(0.6+0.2) + -0.24/(0.6+0.2) = -0.75

5. nn.L1loss

class torch.nn.L1Loss(size_average=None, reduce=None)
功能: 这个用于回归问题,用来计算inputs与target之差的绝对值,可选返回同维度的 tensor 或者是一个标量.
参数:
reduce(bool)- 返回值是否为标量,默认为 True
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。

6. nn.MSELoss

class torch.nn.MSELoss(size_average=None, reduce=None, reduction='elementwise_mean')
计算公式:
在这里插入图片描述

**功能:**计算 output 和 target 之差的平方,可选返回同维度的 tensor 或者是一个标量。
参数:
reduce(bool)- 返回值是否为标量,默认为 True
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。

7. nn.PoissonNLLLoss

class torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='elementwise_mean')
计算公式:
在这里插入图片描述

功能: 用于 target 服从泊松分布的分类任务

参数:
log_input(bool)- 为 True 时,计算公式为:loss(input,target)=exp(input) - target * input;为 False 时,loss(input,target)=input - target * log(input+eps)
full(bool)- 是否计算全部的 loss。例如,当采用斯特林公式近似阶乘项时,此为target*log(target) - target+0.5∗log(2πtarget)
eps(float)- 当 log_input = False 时,用来防止计算 log(0),而增加的一个修正项。即
loss(input,target)=input - target * log(input+eps)
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True

8. nn.KLDivLoss

class torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='elementwise_mean')
计算公式:
在这里插入图片描述
功能:
计算 input 和 target 之间的 KL 散度( Kullback–Leibler divergence) 又称为相对熵,用于描述两个
概率分布之间的差异。计算公式:在这里插入图片描述
其中 p 表示真实分布,q 表示 p 的拟合分布, D(P||Q)表示当用概率分布 q 来拟合真实分布 p 时,产生的信息损耗。
这里的信息损耗,可以理解为损失,损失越低,拟合分布 q越接近真实分布 p。
同时也可以从另外一个角度上观察这个公式,即计算的是 p 与 q 之间的对数差在 p 上的期望值。
特别注意,D(p||q) ≠ D(q||p), 其不具有对称性,因此不能称为 K-L 距离。

信息熵 = 交叉熵 - 相对熵
从信息论角度观察三者,其关系为信息熵 = 交叉熵 - 相对熵。在机器学习中,当训练数据固定,最小化相对熵 D(p||q) 等价于最小化交叉熵 H(p,q) 。

参数:
size_average(bool) 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值,平均值为
element-wise 的,而不是针对样本的平均;为 False 时,返回是各样本各维度的 loss 之和。
reduce(bool) 返回值是否为标量,默认为 True。

使用注意事项:
要想获得真正的 KL 散度,需要如下操作:

  1. reduce = True ;size_average=False
  2. 计算得到的 loss 要对 batch 进行求平均
9. nn.MarginRankingLoss

class torch.nn.MarginRankingLoss(margin=0, size_average=None, reduce=None, reduction='elementwise_mean')
功能:
计算两个向量之间的相似度,当两个向量之间的距离大于 margin,则 loss 为正,小于
margin,loss 为 0。
计算公式:
在这里插入图片描述

y == 1 时,x1 要比 x2 大,才不会有 loss,反之,y == -1 时,x1 要比 x2 小,才不会有 loss。

参数:
margin(float): x1 和 x2 之间的差异。
size_average(bool): 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False时,返回的各样本的 loss 之和。
reduce(bool): 返回值是否为标量,默认为 True。

10. nn.HingeEmbeddingLoss

.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='elementwise_mean')
功能:
未知。为折页损失的拓展,主要用于衡量两个输入是否相似。 used for learning
nonlinear embeddings or semi-supervised 。
计算公式:
在这里插入图片描述
参数:
margin(float)- 默认值为 1,容忍的差距。
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

11. nn.MultiLabelMarginLoss

class torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='element wise_mean')
功能:
用于一个样本属于多个类别时的分类任务。例如一个四分类任务,样本 x 属于第 0
类,第 1 类,不属于第 2 类,第 3 类。
计算公式:
在这里插入图片描述
x[y[j]] 表示 样本 x 所属类的输出值,x[i]表示不等于该类的输出值。

参数:
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。
Input: © or (N,C) where N is the batch size and C is the number of classes.
Target: © or (N,C), same shape as the input.

12. nn.SmoothL1Loss

class torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='elementwise_mea n')
功能:
计算平滑 L1 损失,属于 Huber Loss 中的一种(因为参数 δ 固定为 1 了)。
补充:
Huber Loss 常用于回归问题,其最大的特点是对离群点( outliers )、噪声不敏感,具
有较强的鲁棒性。
公式为:

在这里插入图片描述
理解为,当误差绝对值小于 δ ,采用 L2 损失;若大于 δ ,采用 L1 损失。
回到 SmoothL1Loss,这是 δ =1 时的 Huber Loss。

计算公式为:
在这里插入图片描述

参数:
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

13. SoftMarginLoss

class torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='elementwise_me an')
功能:
Creates a criterion that optimizes a two-class classification logistic loss between input tensor xand
target tensor y (containing 1 or -1). (暂时看不懂怎么用,有了解的朋友欢迎补充!)
计算公式:

在这里插入图片描述

参数:
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

14. MultiLabelSoftMarginLoss

class torch.nn.MultiLabelSoftMarginLoss(weight=None, size_average=None, reduce=None, r eduction='elementwise_mean')

功能:
SoftMarginLoss 多标签版本,a multi-label one-versus-all loss based on max-entropy,
计算公式:

在这里插入图片描述
参数:
weight(Tensor)- 为每个类别的 loss 设置权值。weight 必须是 float 类型的 tensor,其长度要
于类别 C 一致,即每一个类别都要设置有 weight。

15. CosineEmbeddingLoss

class torch.nn.CosineEmbeddingLoss(margin=0, size_average=None, reduce=None, reduction ='elementwise_mean')
功能:
用 Cosine 函数来衡量两个输入是否相似。 used for learning nonlinear embeddings or
semi-supervised 。
计算公式:

在这里插入图片描述

参数:
margin(float)- : 取值范围[-1,1], 推荐设置范围 [0, 0.5]
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

16. MultiMarginLoss

class torch.nn.MultiMarginLoss(p=1, margin=1, weight=None, size_average=None, reduce=N one, reduction='elementwise_mean')
功能:
计算多分类的折页损失。
计算公式:
在这里插入图片描述
其中,0≤y≤x.size(1) ; i == 0 to x.size(0) and i≠y; p==1 or p ==2; w[y]为各类别的
weight。

参数:
p(int)- 默认值为 1,仅可选 1 或者 2。
margin(float)- 默认值为 1
weight(Tensor)- 为每个类别的 loss 设置权值。weight 必须是 float 类型的 tensor,其长度要
于类别 C 一致,即每一个类别都要设置有 weight。
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

17. TripletMarginLoss

class torch.nn.TripletMarginLoss(margin=1.0, p=2, eps=1e- 06, swap=False, size_average=None, reduce=None, reduction='elementwise_mean')
功能:
计算三元组损失,人脸验证中常用。
如下图 Anchor、Negative、Positive,目标是让 Positive 元和 Anchor 元之间的距离尽可能的
小,Positive 元和 Negative 元之间的距离尽可能的大。

在这里插入图片描述
从公式上看,Anchor 元和 Positive 元之间的距离加上一个 threshold 之后,要小于
Anchor 元与 Negative 元之间的距离。
在这里插入图片描述
计算公式:
在这里插入图片描述

参数:
margin(float)- 默认值为 1
p(int)- The norm degree ,默认值为 2
swap(float)– The distance swap is described in detail in the paper Learning shallow convolutional
feature descriptors with triplet losses by V. Balntas, E. Riba et al. Default: False
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False
时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True。

「二分类单标签问题」:nn.BCELoss, nn.BCEWithLogitsLoss, nn.SoftMarginLoss
「二分类多标签问题」:nn.MultiLabelSoftMarginLoss
「多分类单标签问题」: nn.CrossEntropyLoss, nn.NLLLoss, nn.MultiMarginLoss
「多分类多标签问题」: nn.MultiLabelMarginLoss,
「不常用」:nn.PoissonNLLLoss, nn.KLDivLoss
「回归问题」: nn.L1Loss, nn.MSELoss, nn.SmoothL1Loss
「时序问题」:nn.CTCLoss
「人脸识别问题」:nn.TripletMarginLoss
「半监督Embedding问题(输入之间的相似性)」: nn.MarginRankingLoss, nn.HingeEmbeddingLoss, nn.CosineEmbeddingLoss

优化器

Optimizer 的基本属性和方法

class Optimizer(object):

optimizer 对参数的管理是基于组的概念
可以为每一组参数配置特定的lr,momentum,weight_decay 等等。
参数组在 optimizer 中表现为一个 list(self.param_groups),
其中每个元素是dict,表示一个参数及其相应配置,在 dict 中包含’params’、‘weight_decay’、‘lr’ 、'momentum’等字段。

下面我们学习 Pytorch 里面优化器的基本属性:

defaults: 优化器超参数,里面会存储一些学习了,momentum 的值,衰减系数等
state: 参数的缓存,如 momentum 的缓存(使用前几次梯度进行平均)
_step_count: 记录更新次数,学习率调整中使用,比如迭代 100 次之后更新学习率的时候,就得记录这里的 100
param_groups: 管理的参数组,这是个列表,每一个元素是一个字典,在字典中有 key,key 里面的值才是我们真正的参数==(这个很重要,进行参数管理)==
下面我们看一下param_groups是个什么样子:
在这里插入图片描述

可以看到,param_groups,是一个list,list里面有1个dict,dict下有6个键值对,这6个键值对中有一个key是“params”其对应的value是一个长度为32的list,这个list里面存的就是每一层的权重和偏置,类型都为tensor

基本属性:
Optimizer.zero_grad():清空所管理参数的梯度, 里注意Pytorch有一个特性就是「张量梯度不自动清零」
Optimizer.step(): 执行一步更新
Optimizer.add_param_group(): 添加参数组,我们知道优化器管理很多参数,这些参数是可以分组的,我们对不同组的参数可以设置不同的超参数,比如模型 finetune 中,我们希望前面特征提取的那些层学习率小一些,而后面我们新加的层学习率大一些更新快一点,就可以用这个方法

optimizer = torch.optim.SGD(net.parameters(), 0.001, momentum=0.1)
w = torch.randn((3, 3), requires_grad=True)  # 随机新建一组参数
optimizer.add_param_group({"params": w, "lr": 0.0000001})  # 把新建的参数传到优化器中,可以自己制定学习率

Optimizer.state_dict(): 获取优化器当前状态信息字典
保存优化器信息:

torch.save(optimizer.state_dict(), "optimizer.pkl")

Optimizer.load_state_dict(): 加载状态信息字典,这两个方法用于模型断点的一个续训练, 所以我们在模型训练的时候,一般多少个 epoch 之后就要保存当前的状态信息。

optimizer2 = torch.optim.Adam(torch.tensor([1, 2, 3]))  # 先建一个优化器,并且随便传一个初始参数
state_dict = torch.load("optimizer.pkl")    # 读取保存的pickle模型
optimizer2.load_state_dict(state_dict)  # 这样保存的优化器的参数就读取到了新建的优化器中了

常用优化器

  1. optim.SGD: 随机梯度下降法
    class torch.optim.SGD(params, lr=<objectobject>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
    功能:
    可实现 SGD 优化算法,带动量 SGD 优化算法,带 NAG(Nesterov accelerated
    gradient)动量 SGD 优化算法,并且均可拥有 weight_decay 项。
    参数:
    params(iterable) 参数组,优化器要管理的那部分参数。
    lr(float) 初始学习率,可按需随着训练过程不断调整学习率。
    momentum(float) 动量,通常设置为 0.9,0.8
    dampening(float) dampening for momentum ,暂时不了其功能,在源码中是这样用的:buf.mul_(momentum).add_(1 - dampening, d_p),值得注意的是,若采用nesterov,dampening 必须为 0.
    weight_decay(float) 权值衰减系数,也就是 L2 正则项的系数
    nesterov(bool) bool 选项,是否使用 NAG(Nesterov accelerated gradient)

  2. optim.Adagrad: 自适应学习率梯度下降法
    class torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial _accumulator_value=0)
    功能:
    实现 Adagrad 优化方法( Adaptive Gradient ),Adagrad 是一种自适应优化方法,是自适
    应的为各个参数分配不同的学习率。这个学习率的变化,会受到梯度的大小和迭代次数的
    影响。梯度越大,学习率越小;梯度越小,学习率越大。缺点是训练后期,学习率过小,
    因为 Adagrad 累加之前所有的梯度平方作为分母。
    详细公式请阅读:Adaptive Subgradient Methods for Online Learning and
    Stochastic OptimizationJohn Duchi, Elad Hazan, Yoram Singer; 12(Jul):2121−2159,2011.

  3. optim.RMSprop: Adagrad 的改进
    class torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e- 08, weight_decay=0, momentum=0, centered=False)
    功能:
    实现 RMSprop 优化方法(Hinton 提出),RMS 是均方根( root meam square )的意
    思。RMSprop 和 Adadelta 一样,也是对 Adagrad 的一种改进。RMSprop 采用均方根作为分
    母,可缓解 Adagrad 学习率下降较快的问题,并且引入均方根,可以减少摆动,详细了解可读:

  4. optim.Adadelta: Adagrad 的改进
    class torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e- 06, weight_decay=0)
    功能:
    实现 Adadelta 优化方法。Adadelta 是 Adagrad 的改进。Adadelta 分母中采用距离
    当前时间点比较近的累计项,这可以避免在训练后期,学习率过小。
    详细公式请阅读:

  5. optim.Adam: RMSprop 结合 Momentum
    class torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e- 08, weight_decay=0, amsgrad=False)
    功能:
    实现 Adam(Adaptive Moment Estimation))优化方法。Adam 是一种自适应学习率的优
    化方法,Adam 利用梯度的一阶矩估计和二阶矩估计动态的调整学习率。吴老师课上说过,
    Adam 是结合了 Momentum 和 RMSprop,并进行了偏差修正。
    参数:
    amsgrad- 是否采用 AMSGrad 优化方法,asmgrad 优化方法是针对 Adam 的改进,通过添加
    额外的约束,使学习率始终为正值。(AMSGrad,ICLR-2018 Best-Pper 之一,《 On the
    convergence of Adam and Beyond 》)。
    详细了解 Adam 可阅读,Adam: A Method for StochasticOptimization

  6. optim.Adamax: Adam 增加学习率上限
    class torch.optim.Adamax(params, lr=0.002, betas=(0.9, 0.999), eps=1e- 08, weight_decay=0)
    功能:
    实现 Adamax 优化方法。Adamax 是对 Adam 增加了一个学习率上限的概念,所以也称之
    为 Adamax。
    详细了解可阅读,Adam:A Method for StochasticOptimization(没错,就是 Adam 论文中提出了Adamax)。

  7. optim.SparseAdam: 稀疏版的 Adam
    class torch.optim.SparseAdam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08)
    功能:
    针对稀疏张量的一种“阉割版”Adam 优化方法。
    only moments that show up in the gradient get updated, and only those
    portions of the gradient get applied to the parameters

  8. optim.ASGD: 随机平均梯度下降
    class torch.optim.ASGD(params, lr=0.01, lambd=0.0001, alpha=0.75, t0=1000000. 0, weight_decay=0)
    功能:
    ASGD 也成为 SAG,均表示随机平均梯度下降( Averaged Stochastic Gradient
    Descent ),简单地说 ASGD 就是用空间换时间的一种 SGD,详细可参看论文:
    参数:
    params(iterable)参数组(参数组的概念请查看 3.1 优化器基类:Optimizer),优化器
    要优化的那些参数。
    lr(float)初始学习率,可按需随着训练过程不断调整学习率。
    lambd(float)衰减项,默认值 1e-4。
    alpha(float)power for eta update ,默认值 0.75。
    t0(float)point at which to start averaging,默认值 1e6。
    weight_decay(float)权值衰减系数,也就是 L2 正则项的系数。

  9. optim.Rprop: 弹性反向传播
    class torch.optim.Rprop(params, lr=0.01, etas=(0.5, 1.2), step_sizes=(1e-06, 50))
    功能:
    实现 Rprop 优化方法(弹性反向传播),优化方法原文《Martin Riedmiller und Heinrich Braun: Rprop - A Fast Adaptive Learning Algorithm. Proceedings of the International Symposium on Computer and Information Science VII, 1992》
    该优化方法适用于 full-batch,不适用于 mini-batch,因而在 mini-batch 大行其道的时代
    里,很少见到。

  10. optim.LBFGS: BFGS 的改进
    class torch.optim.LBFGS(params, lr=1, max_iter=20, max_eval=None, tolerance_ grad=1e-05, tolerance_change=1e-09, history_size=100, line_search_fn=None)
    功能:
    实现 L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno)优化方法。
    L-BFGS 属于拟牛顿算法。L-BFGS 是对 BFGS 的改进,特点就是节省内存。

这里面比较常用的就是optim.SGDoptim.Adam, 其他优化器的详细使用方法移步官方文档。

调整学习率

调整学习率的基类:

class _LRScheduler(object):

主要属性:
optimizer:关联的优化器, 得需要先关联一个优化器,然后再去改动学习率
last_epoch:记录epoch数, 学习率调整以epoch为周期
base_lrs:记录初始学习率

主要方法:
step():更新下一个epoch的学习率, 这个是和用户对接
get_lr():虚函数, 计算下一个epoch的学习率, 这是更新过程中的一个步骤(一般会返回当前epoch的学习率)

1. lr_scheduler.StepLR 功能:等间隔调整学习率

class torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, las t_epoch=-1)
功能:
等间隔调整学习率,调整倍数为 gamma 倍,调整间隔为 step_size。间隔单位是step。需要注意的是,step 通常是指 epoch,不要弄成 iteration 了。
参数:
step_size(int) 学习率下降间隔数,若为 30,则会在 30、60、90…个 step 时,将学习率调整为 lr*gamma。
gamma(float)学习率调整倍数,默认为 0.1 倍,即下降 10 倍。
last_epoch(int)上一个 epoch 数,这个变量用来指示学习率是否需要调整。当last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始值。
实例:

optimizer = torch.optim.SGD(net.parameters(), 0.001, momentum=0.1)
schedule = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
for epoch in range(MAX_EPOCH):

    for i, data in enumerate(train_loader):
        # forward
        inputs, labels = data
        outputs = net(inputs)

        # Compute loss
        optimizer.zero_grad()
        loss = criterion(outputs, labels)

        # backward
        loss.backward()

        # updata weights
        optimizer.step()

    schedule.step(loss_value)	# 这里传入检测值,这里是按照损失函数的loss,也可以用其他标准,比如准确率... ...

2. lr_scheduler.MultiStepLR:按给定间隔调整学习率

class torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1)
功能:
按设定的间隔调整学习率。这个方法适合后期调试使用,观察 loss 曲线,为每个实验定制学习率调整时机。
参数:
milestones(list)一个 list,每一个元素代表何时调整学习率,list 元素必须是递增的。如 milestones=[30,80,120]
gamma(float)学习率调整倍数,默认为 0.1 倍,即下降 10 倍。
last_epoch(int)上一个 epoch 数,这个变量用来指示学习率是否需要调整。当last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始值。

3. lr_scheduler.ExponentialLR:按指数衰减调整学习率

class torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch=-1)
功能:
按指数衰减调整学习率,调整公式: l r = l r ∗ g a m m a e p o c h lr = lr * gamma^{epoch} lr=lrgammaepoch
参数:
gamma 学习率调整倍数的底,指数为 epoch,即 gamma**epoch
last_epoch(int) 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始值。

4. lr_scheduler.CosineAnnealingLR:余弦周期调整学习率

class torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_ min=0, last_epoch=-1)
功能:
以余弦函数为周期,并在每个周期最大值时重新设置学习率
参数:
T_max表示下降周期,只是往下的那一块。比如T_max=100,表示100个epoch学习率从最高走到最低,100就是1/2个余弦周期。
eta_min表示学习率下限,
学习率调整公式:
在这里插入图片描述

5. lr_scheduler.ReduceLROnPlateau:监控指标调整学习率(超级实用)

class torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min',factor=0.1, patience=10, verbose=False, threshold=0.0001, threshold_mode='rel', c ooldown=0, min_lr=0, eps=1e-08)
功能:
当指标不再变化则调整, 这个非常实用。
可以监控loss或者准确率,当不在变化的时候,我们再去调整。这是非常nice的学习率调整策略。
例如,当验证集的 loss 不再下降时,进行学习率调整;或者监测验证集的 accuracy,当accuracy 不再上升时,则调整学习率。
参数:
mode(str) 模式选择,有 min 和 max 两种模式,min 表示当指标不再降低(如监测loss),max 表示当指标不再升高(如监测accuracy)。
factor(float) 学习率调整倍数(等同于其它方法的 gamma),即学习率更新为 lr = lr *factor
patience(int) 直译——“耐心”,即忍受该指标多少个 step 不变化,当忍无可忍时,调整学习率。
cooldown(int) “冷却时间“,当调整学习率之后,让学习率调整策略冷静一下,让模型再训练指定的epoch数,再重启监测模式。
verbose(bool) 是否打印学习率信息。
min_lr(float or list) 学习率下限,可为 float,或者 list,当有多个参数组时,可用 list 进行设置。
eps(float) 学习率衰减的最小值,当学习率变化小于 eps 时,则不调整学习率。

threshold(float) Threshold for measuring the new optimum,配合 threshold_mode 使用。
threshold_mode(str) 选择判断指标是否达最优的模式,有两种模式,rel 和 abs。

当 threshold_mode==rel,并且 mode==max 时,dynamic_threshold = best * ( 1 +threshold );
当 threshold_mode==rel,并且 mode==min 时,dynamic_threshold = best * ( 1 -threshold );
当 threshold_mode==abs,并且 mode==max 时,dynamic_threshold = best + threshold ;
当 threshold_mode==rel,并且 mode==max 时,dynamic_threshold = best - threshold

6. lr_scheduler.LambdaLR

class torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)
功能:
为不同参数组设定不同学习率调整策略。调整规则为,lr = base_lr *
lmbda(self.last_epoch) 。
参数:
lr_lambda(function or list)一个计算学习率调整倍数的函数,输入通常为 step,当有多个参数组时,设为 list。
last_epoch(int) 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始值。
例如:

import torch.optim as optim
ignored_params = list(map(id, net.fc3.parameters()))
base_params = filter(lambda p: id(p) not in ignored_params, net.parameters())
optimizer = optim.SGD([
{'params': base_params},
{'params': net.fc3.parameters(), 'lr': 0.001*100}],
    0.001, momentum=0.9,weight_decay=1e-4)
lambda1 = lambda epoch: epoch // 3
lambda2 = lambda epoch: 0.95 ** epoch

scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=[lambda1,
lambda2])
for epoch in range(100):
scheduler.step()
print('epoch: ', i, 'lr: ', scheduler.get_lr())

防止过拟合的方法

1. L2正则化

理论:
我们下面通过代码来学习 Pytorch 中的 L2 正则项, 在 Pytorch 中, L2 正则项又叫做 weight decay (权值衰减)。那么为啥这个东西叫做权值衰减呢?怎么衰减了? 我们这样看:首先,我们原来的时候, 参数的更新公式是这样的:

w i + 1 = w i − ∂ L o s s ∂ w i w_{i+1}=w_i-\frac{\partial Loss}{\partial w_i} wi+1=wiwiLoss
而现在,我们的 Obj 加上了一个 L2 正则项在这里插入图片描述
那么参数的更新方式也就变成了下面这个:
在这里插入图片描述

代码:

optimizer = torch.optim.SGD (net.parameters (), lr=0.01, momentum=0.9, weight_decay=1e-2)

L2 正则使用也比较简单,就是在优化器里面指定 weight_decay 这个参数即可。

2. torch.nn.Droupout

def dropout(input, p=0.5, training=True, inplace=False):
    # type: (Tensor, float, bool, bool) -> Tensor

p 是概率,也就是 不起作用的神经元占比。
实例:

       self.linears = nn.Sequential (

            nn.Linear (1, neural_num),
            nn.ReLU (inplace=True),

            nn.Dropout (d_prob),             # 注意这里用上了 Dropout, 我们看到这个 Dropout 是接在第二个 Linear 之前,Dropout 通常放在需要 Dropout 网络的前一层
            nn.Linear (neural_num, neural_num),
            nn.ReLU (inplace=True),

            nn.Dropout (d_prob),
            nn.Linear (neural_num, neural_num),
            nn.ReLU (inplace=True),

            nn.Linear (neural_num, 1),
        )

3. BatchNorm

Pytorch 中提供了三种 BatchNorm 方法:

  • nn.BatchNorm1d
  • nn.BatchNorm2d
  • nn.BatchNorm3d

上面三个 BatchNorm 方法都继承 __BatchNorm 这个基类,初始化参数如下:

这里的 num_features 表示一个样本的特征数量,这是最重要的一个参数。
eps 表示分母修正项, momentum 表示指数加权平均估计当前 mean/var。
affine 表示是否需要 affine transform,
track_running_stats 表示是训练状态还是测试状态,这个也是非常关键的,因为我们发现 momentum 那里有个均值和方差,如果是训练状态,那么就需要重新估计 mean 和方差,而如果是测试状态,就用训练时候统计的均值和方差。

而 BatchNorm 的三个方法也是有属性的:

  1. running_mean: 均值
  2. running_var: 方差
  3. weight: affine transform 中的 β
  4. bias: affine transforom 中的 γ

这四个属性分别对应我们公式中用到的四个属性:
在这里插入图片描述

小结一下

有序调整:Step、MultiStep、 Exponential和CosineAnnealing, 这些得事先知道学习率大体需要在多少个epoch之后调整的时候用
自适应调整:ReduceLROnPleateau, 这个非常实用,可以监控某个参数,根据参数的变化情况自适应调整
自定义调整:Lambda, 这个在模型的迁移中或者多个参数组不同学习策略的时候实用
调整策略就基本完了,那么我们得先有个初始的学习率啊, 下面介绍两种学习率初始化的方式:

设置较小数:0.01, 0.001, 0.0001
搜索最大学习率:看论文《Cyclical Learning Rates for Training Neural Networks》, 这个就是先让学习率从0开始慢慢的增大,然后观察acc, 看看啥时候训练准确率开始下降了,就把初始学习率定为那个数。

各种注意事项

  1. torchvision.transforms.ToTensor()

Convert a PIL Image or numpy.ndarray to tensor.
Converts a PIL Image or numpy.ndarray (H x W x C) in the range
[0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0]
if the PIL Image belongs to one of the modes (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1)
or if the numpy.ndarray has dtype = np.uint8
In the other cases, tensors are returned without scaling.
转化PIL或者numpy.ndarray图像,将[0,255]缩放到[0.0,1.0],格式转化为torch.FloatTensor,且将 (H x W x C)转化为(C x H x W)(自带这个功能,千万别自己转)

img=PIL.Image.open("/home/gsh/Pictures/2020-09-04 08-46-44 的屏幕截图.png")
trm=torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])
img2=trm(img)
# cv 也行,读进去是numpy.ndarray
img_p = cv2.imread()
img2=trm(img_p)

2.with torch.no_grad():通过使用这个包裹代码,实现阻止autograd去跟踪那些tensor.requesgrad=True的情况,多用于测试阶段.

	with torch.no_grad():
    for data in testloader:
        images, labels = data#batchsize=4故label的格式是tensor([3, 8, 8, 0])
        print(labels)
        output = net(images)
        _, predicted = torch.max(output.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
  1. 由于pytorch的梯度在反向传播中的中间变量的缓存会被清空,所以需要多次反向传播时,需要指定def backward(self, gradient=None, retain_graph=None, create_graph=False):
    中的retain_graph=None为true
    但官方文档又说:(这里有些疑惑)
retain_graph (bool, optional): If ``False``, the graph used to compute
                the grads will be freed. Note that in nearly all cases setting
                this option to True is not needed and often can be worked around
                in a much more efficient way. Defaults to the value of
                ``create_graph``.
create_graph (bool, optional): If ``True``, graph of the derivative will
                be constructed, allowing to compute higher order derivative
                products. Defaults to ``False``.
  1. 训练流程
		# 前向传播
        outputs = net(inputs)
        #计算损失函数
        loss = criterion(outputs, labels)
        # 清空梯度(一定要先清空梯度,否则会梯度累加
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 更新参数
        optimizer.step()

对比之前的做法

class VGG(nn.Module):
    def __init__(self, feature, num_classes=5, init_weight=False):
        super(VGG, self).__init__()
        self.feature = feature
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(512 * 7 * 7, 2048),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(True),
            nn.Linear(2048, num_classes)
        )
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值