随笔记(二)

 

2018.06.03 Python代码生成prototxt

 

如下代码生成peleenet.prototxt。Python文件和deploy文件的 github 链接 . 求个小心心 --

#coding: utf-8
#by Chen yh

tran_channel = 32  #growth_rate
class Genpelee():

    def __init__(self):
        self.last = "data"

    def header(self,name):
        s_name = "name: \"%s\"" %name
        return s_name

    def input(self,size):
        s_input = """
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param {
    shape {
      dim: 1
      dim: 3
      dim: %d
      dim: %d
    }
  }
}""" %(size,size)
        return s_input

    def conv(self, name, num_output, k_size, stride, bias_term = False, bottom = None):

        if bottom is None:
            bottom = self.last

        if bias_term:
            bias_str = ""
            bias_filler = """
    bias_filler {
      type: "constant"
    }        
"""
        else:
            bias_str = "\n    bias_term: false"
            bias_filler = ""

        if k_size>1 :
            pad_str = "\n    pad: %d" %(int(k_size/2))
        else:
            pad_str = ""

        s_conv = """
layer {
  name: "%s"
  type: "Convolution"
  bottom: "%s"
  top: "%s"
  convolution_param {
    num_output: %d%s%s
    kernel_size: %d
    stride: %d
    weight_filler {
      type: "xavier"
    }%s
  }
}""" %(name,bottom,name,num_output,bias_str,pad_str,k_size,stride,bias_filler)

        self.last = name
        return s_conv

    def bn(self,bottom = None):

        if bottom is None:
            bottom = self.last

        s_bn = """
layer {
  name: "%s/bn"
  type: "BatchNorm"
  bottom: "%s"
  top: "%s"
  param {
    lr_mult: 0.0
    decay_mult: 0.0
  }
  param {
    lr_mult: 0.0
    decay_mult: 0.0
  }
  param {
    lr_mult: 0.0
    decay_mult: 0.0
  }
  batch_norm_param {
    moving_average_fraction: 0.999000012875
    eps: 0.0010000000475
  }
}
layer {
  name: "%s/scale"
  type: "Scale"
  bottom: "%s"
  top: "%s"
  scale_param {
    filler {
      value: 1.0
    }
    bias_term: true
    bias_filler {
      value: 0.0
    }
  }
}""" %(bottom,bottom,bottom,bottom,bottom,bottom)
        return s_bn

    def relu(self,bottom = None):

        if bottom is None:
            bottom = self.last

        s_relu = """
layer {
  name: "%s/relu"
  type: "ReLU"
  bottom: "%s"
  top: "%s"
}""" %(bottom,bottom,bottom)
        return s_relu

    def pool(self, name, k_size, stride, bottom = None):
        if bottom is None:
            bottom = self.last

        s_pool = """
layer {
  name: "%s"
  type: "Pooling"
  bottom: "%s"
  top: "%s"
  pooling_param {
    pool: MAX
    kernel_size: %d
    stride: %d
  }
}""" %(name,bottom,name,k_size,stride)
        self.last = name
        return s_pool

    def ave_pool(self, bottom = None):
        if bottom is None:
            bottom = self.last

        s_avepool = """
layer {
  name: "global_pool"
  type: "Pooling"
  bottom: "%s"
  top: "global_pool"
  pooling_param {
    pool: AVE
    global_pooling: true
  }
}""" %(bottom)
        self.last = "global_pool"
        return s_avepool

    def cls(self, num = 1000, bottom = "global_pool"):
        s_cls = """
layer {
  name: "classifier"
  type: "InnerProduct"
  bottom: "%s"
  top: "classifier"
  inner_product_param {
    num_output: %d
    bias_term: true
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "prob"
  type: "Softmax"
  bottom: "classifier"
  top: "prob"
}""" %(bottom,num)
        return s_cls

    def concat(self,bottom,top):
        assert type(bottom) is list

        length = len(bottom)
        str_bo = ""
        for i in range(length):
            str_bo += """
  bottom: "%s" """ %(bottom[i])

        s_concat = """
layer {
  name: "%s"
  type: "Concat"%s
  top: "%s"
  concat_param {
    axis: 1
  }
}""" %(top,str_bo,top)
        self.last = top
        return s_concat

    def conv_block(self, name, num_output, k_size, stride, bottom = None):
        if bottom is None:
            bottom = self.last

        s_conv_block = self.conv(name, num_output, k_size, stride, bottom = bottom)+self.bn()+self.relu()
        return s_conv_block

    def stem_block(self,init_feature):

        s1 = self.conv_block("stem1",init_feature,3,2)
        s2_a = self.conv_block("stem2a",int(init_feature/2),1,1)
        s2_b = self.conv_block("stem2b",init_feature,3,2)
        s2_c = self.pool("stem1/pool",2,2,bottom= "stem1")
        s3 = self.concat(["stem2b","stem1/pool"],"stem/concat")+self.conv_block("stem",init_feature,1,1)
        stem = s1+s2_a+s2_b+s2_c+s3
        return stem

    def dense_block(self, num_layers, bottleneck_width, stage, growth_rate=32, bottom=None):
        if bottom is None:
            bottom = self.last

        g = int(growth_rate / 2)
        btn_width = g * bottleneck_width
        s_dense = ""

        for i in range(1, num_layers+1):
            base_name = "stage"+str(stage)+"_"+str(i)
            branch_1 = self.conv_block(base_name+"/a1",btn_width,1,1)+self.conv_block(base_name+"/a2",g,3,1)
            branch_2 = self.conv_block(base_name+"/b1",btn_width,1,1,bottom=bottom)+self.conv_block(base_name+"/b2",g,3,1)+self.conv_block(base_name+"/b3",g,3,1)
            concat = self.concat([bottom,base_name+"/a2",base_name+"/b3"],base_name)
            s_dense += branch_1 + branch_2 + concat
            bottom = self.last

        assert self.last == base_name
        return s_dense

    def transition_layer(self, growth_rate=32, has_pool = True, bottom = None):
        if bottom is None:
            bottom = self.last

        name,num = bottom.split("_",1)
        global tran_channel
        tran_channel += (int(num))*growth_rate
        s_tran = self.conv_block(name, tran_channel,1,1)

        if has_pool:
            s_tran += self.pool(name+"/pool",2,2)

        return s_tran

    def generate(self):
        s_steam = self.header("PeleeNet")+self.input(224)+self.stem_block(32)
        s_stage1 = self.dense_block(3,1,1)+self.transition_layer()
        s_stage2 = self.dense_block(4,2,2)+self.transition_layer()
        s_stage3 = self.dense_block(8,4,3)+self.transition_layer()
        s_stage4 = self.dense_block(6,4,4)+self.transition_layer(has_pool=False)
        s_cls = self.ave_pool()+self.cls()
        s_net = s_steam+s_stage1+s_stage2+s_stage3+s_stage4+s_cls
        return s_net

if __name__ == "__main__":
    gen = Genpelee()
    s= gen.generate()
    with open("PeleeNet.prototxt",'w') as a:
        a.write(s)

记录一下用Python 生成prototxt的问题吧。

1. 目前比较常用的方法是

from caffe import layers as L
from caffe import params as P

这种方法生成train.prototxt 加学习率等参数时,注意param必须是一个列表,元素是字典。BN,relu等层用到In place时要写出来。参考如下代码。参考链接链接2

#coding:utf-8
#by Chen yh

import  caffe
from caffe import layers as l
from caffe import params as p
from caffe.proto import caffe_pb2
from google.protobuf import text_format


if __name__=="__main__":
    net = caffe.NetSpec()
    net.data=l.Input(shape=dict(dim=[1,3,224,224]))

    param=[dict(lr_mult=1,decay_mult=1)]
    net.conv1 = l.Convolution(net.data,kernel_size=3,num_output=56,pad=1,group=1,stride=1,bias_term=False,weight_filler=dict(type="msra"),param=param)

    bn_param=dict(param=[dict(lr_mult=0,decay_mult=0),dict(lr_mult=0,decay_mult=0),dict(lr_mult=0,decay_mult=0)])
    net["conv1/bn"] = l.BatchNorm(net.conv1,in_place=True,**bn_param)

    s_p=[dict(lr_mult=1,decay_mult=0),dict(lr_mult=2,decay_mult=0)]
    net["conv1/scale"] = l.Scale(net["conv1/bn"],in_place=True,bias_term=True,param=s_p,filler=dict(value=1),bias_filler=dict(value=0))

    net["conv1/relu"] = l.Relu(net["conv1/scale"],in_place=True)

    net.pool1 = l.Pooling(net["conv1/relu"],pool=p.Pooling.MAX,kernel_size=2,stride=2)
    print net.to_proto()

2. 第二种是我这种写法,我个人觉得这种写法比较好,不用记那么多接口参数。其主要思想就是写出基本的conv,bn,relu等结构,然后定义block,最后统一连接好各个block.

要特别注意的点是,self.last的更新一定要正确。在复杂的block结尾可以用assert来确保net的传递正确性。

 

 


2018.07.10 caffePython接口进行数据可视化

 代码是以自己稍加改进的mobilenet_ssd为例,希望看到几个检测层的输出情况,从而决定如何做fuse。以conv11为例。

import numpy as np
import sys,os
import cv2
root = "/home/its/ssd-depthwize/caffe/python/"
sys.path.append(root)
import caffe
import matplotlib.pyplot as plt

def preposs(img):
    img = cv2.resize(img, (308, 308))
    img = img.transpose([2, 0, 1])
    img = (img - 127.5) / 0.007843
    img = img.astype(np.float32)
    return img

def vis_square(data):
    """Take an array of shape (n, height, width) or (n, height, width, 3)
       and visualize each (height, width) thing in a grid of size approx. sqrt(n) by sqrt(n)"""

    # normalize data for display
    data = (data - data.min()) / (data.max() - data.min())

    # force the number of filters to be square
    n = int(np.ceil(np.sqrt(data.shape[0])))
    padding = (((0, n ** 2 - data.shape[0]),
                (0, 1), (0, 1))  # add some space between filters
               + ((0, 0),) * (data.ndim - 3))  # don't pad the last dimension (if there is one)
    data = np.pad(data, padding, mode='constant', constant_values=1)  # pad with ones (white)

    # tile the filters into an image
    data = data.reshape((n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1)))
    data = data.reshape((n * data.shape[1], n * data.shape[3]) + data.shape[4:])

    cv2.imshow("conv11",data)
    cv2.waitKey(0)


def show(net,img):
    net.blobs["data"].data[...] = img
    net.forward()
    data_l = net.blobs["conv11"].data
    data_l = data_l.squeeze()
    vis_square(data_l)

if __name__ == "__main__":
    img = cv2.imread("004545.jpg")
    img = preposs(img)

    p = "MobileNetSSD_test.prototxt"
    m = "_iter_17000.caffemodel"
    caffe.set_mode_gpu()
    net = caffe.Net(p,m,caffe.TEST)

    show(net,img)

 数据预处理,我没有采用caffe自带的方法,也就是caffe.io.load_image(),和一堆transform。使用方法可以参考shicai的mobilenet里面的eval_image.py, github。具体细节参考/caffe/python/caffe/io.py,定义了一堆接口。同目录下的classifier.py作为官方例程也调用了这么些接口。

我是自己写函数来进行预处理操作,注意的点有:1. CV2读取的img就是BRG格式,不是RGB。因此不需要交换channel层内部维度,只需要transpose把channel调到第一个维度即可。2. 注意np.float32的使用。

主要说明一下vis_square函数的作用,其可以可视化卷积核,也可以可视化输出的blob。以blob为例:

首先,n=[sqrt(channel)]+1=23,ceil函数在pool层也用到过。接下来使用np.pad,一来可以是不同的feature map之间可以留个白条做记号,二来是补全channel==n^2.关于pad函数的使用,参考这篇博客。接下里第一个reshape是把(529,21,21)分组为(23,23,21,21),这个好理解,reshape不改变内部数据排列,单纯地把529channel分成了23“行”和23“列”,每一个元素是21*21的feature map。但是要想在二维图上连续显示出来,这样排列数据可不行(这样的话每个map的数据排在一起,也就导致作图每一行是一个map,显然不行,所以需要按照下图格式重新排列axis。我是倒着理解这个transpose的,数据排列必须改成小行->大行->小列->大列,才可以把这些map展示成正常的上图的输出格式。其实这样的思路在SSD中也有用到,以19*19map为例,loc层输出batch*(6*4)*19*19,此时blob的数据排列是依次排列每个特征图上的点的同一个anchor的同一个偏移坐标。通俗的来说,对于19*19上的每个点,6个prior box的4个坐标信息分布在不同channel的特征图的同一个位置上,所以在flatten之前要把每个点的每个anchor信息放一起,而不是说单纯的24*19*19展开就好了。因此ssd有了permute层,也就相当于transpose。

第二个reshape就好理解了,转化为展示图的height*width即可。

2018.09.04 python代码

经常会看到一些好的函数,现在开始记录一下。

首先生成了一个3*3的升序矩阵,np.random.shuffle()是一个随机矩阵,如果直接输入a,则会按照行随机,但是每行内顺序不变,要想对a所有的元素随机,则需要flatten()函数;np.argmax,np.argmin分别返回np的最大值索引,最小值索引;np.argsort函数当输入一维矩阵时,对所有元素升序排序输出索引表,当输入多维矩阵时,需要直接axis,axis=1按行,默认按照行升序排序,要想对所有元素进行排序得到索引表,需要用flatten(),最后一行的函数是将a所有元素进行升序排序。

np.where返回的是一个元组,元素个数是矩阵的维度,每个元素里面的值对应的是符合条件的值在当前维的索引,所以每个元素的对应的值组合到一起就是符合条件的值的索引; np.empty返回一个和输入shape一样大的array,值一般不为0,所以其实并不是"空"矩阵.给定的shape某个元素可以是0,也能被索引,图片中均给出示例.empty_like是创造一个和给定矩阵一样shape的矩阵;np.hstacknp.vstack是垛堞列和垛堞行,输入只有一个选项,要合并的矩阵放到一个列表里面.

[:-1]可以看成切片;[::-1]是对矩阵最外面维度进行倒序排列,如果是一维矩阵,相当于倒序排列原矩阵.

np.savetxt在存储矩阵数据时,常常用了科学计数法保存,如何修改成其他格式呢?使用fmt. np.savetxt(file,array,fmt="%s")即为保存为字符串类型,fmt=%d,整形,%f,float类型

[[] for _ in range(k)]生成k个小列表

使用mpl_toolkits模块画世界地图.

xlrd读excel快,xlwt写excel不能超过65536行,openpyxl可以写大型excel数据.

f = xlrd.open_workbook("graph.xlsx").sheet_by_name("Sheet")
    data = []
    for i in range(f.nrows):
        if type(f.cell(i,1).value)==float:
            data.append([f.cell(i,j).value for j in range(f.ncols)])
from openpyxl import Workbook
 book = Workbook()
    ws = book.active
    for i in range(1,label.shape[0]+1):
        ws.cell(row=i,column=1,value=label[i-1,0])
    book.save(filename = "1.xlsx")

python的setdefault方法 。参考链接setdefault

pytorch中经常用到OrderedDict.在遍历字典的时候对字典进行操作,经常会报RuntimeError: OrderedDict mutated during iteration错误。例如下面的代码:

from collections import OrderedDict

a=OrderedDict() # a是一个空的有序字典,b是一个已经存在且有键值的字典

for k,v in b.items():

    k_newname = k + "_new"
    b[k_newname] = v  #会报错,因为遍历字典的时候不允许增添键值对,否则循环出出错。应该为a[k_newname] = v

错误很幼稚,但是有时候代码长了还是会犯,在此记录一下。

 

数据集问题解决记录:

1.数据集中样本不均衡问题;

2.数据集中图片大小差别太大;

 

2018.11.20

花了很长时间写论文,感觉啃了很长时间老本。年底准备好好提升一下自己的工程能力和进行必要的总结,准备明年的找工作,并冲一波ICCV。

1. group conv的一个精髓就在于concat, 如果不concat,换成sum,有效果吗?

其实group的concat换成sum,那就等效于普通的conv了。在resnext论文里有幅图,1,3两种情况最后一个block就相当于我提出的情景;

2. depthwise是一个高效的设计。本人看channel-wise过程是把dw和pw一直看成是一个conv的分解过程。所以在一般input-channel>>9的时候,dw+pw的计算量几乎全在pw上。因此如何降低pw计算量成为了提高小网络效率的关键。1 int8量化pw。ncnn没记错就是这样实现的,具体细节参看代码,我也没怎么用过(任重道远)2. shufflenet使用了group来降低pw计算量,本质是g conv代替conv进一步降低了计算量,(这里的g conv和conv指的是dw+pw合成的conv,其实是不存在的)(用dw分解gconv同样降低了约9倍计算量,其实只要是用dw+pw近似conv就是降低约9倍的计算量),当然第一个1*1也变成了g conv。3. 我自己天马行空想的。1*1卷积无外乎是一个线性加权的作用,弥补传统卷积中不同channel的spatial 过程结果的不一致变成dw后完全一致的缺陷,当然他还能起到升维降维的作用。能不能不要这些参数,直接打到一个加权的目的呢?当需要降维的时候,沿着channel维度做group的avg/max pool,其实这有点像spatial attention,或者说maxout. 需要升维的时候, 沿着channel维度可做或不做gruop的一维线性插值,这样直接就省去了计算量。至于维度不变的情况,要考虑到特征的多样性,不能简单的进行插值这样的操作,我自己拍脑袋还没想到。好想训练一个imagenet的分类模型试验自己的idea,可是我只有一张卡。悲伤脸--。

 

2018.12.24

图像处理操作,很常用,但是真的去写会发现容易出一些小问题,在此随便小结一下。

cv2读进来的是BGR顺序的图片,plt显示需要RGB正常的图片,所以显示出来不正常

调换顺序后,输出正常了,此外可以发现调换通道顺序是在第三维进行的,所以很多在cv2.imread之后,接了transpose((2,0,1)),这是为了将通道调到第一维,例如300*300*3 -> 3*300*300。

水平翻转其实是行的像素值倒序排,也就是第二维元素。竖直翻转是列的像素值倒序排列。

 

2019.1.9 pytorch小记

1. param_groups.

spe_params = list(net.loc.parameters()) + list(net.conf.parameters())
spe_params_id = list(map(id,spe_params))
pre_params = list(filter(lambda x: id(x) not in spe_params_id, net.parameters()))
params_group = [{'params': spe_params},
                {'params': pre_params,'lr':0.0}]
optimizer = optim.SGD(params_group, lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay)

这里net.loc和net.conf已经是modulelist了,所以可以直接写出他们的parameters,如果是几个层,建议用nn.Modulelist转换为一个module的list,然后在后面传入的时候加一个.parareters()即可。

param-groups中每一个元素都有lr,moment,wd等超参数,在调整的时候对每个param_group赋值就好。

param_groups是一个列表,每个元素是一个字典,键是各个超参数。

需要注意的是,冻结某些层的时候,如果BN在里面,running_mean和var还是在更新,要想直接冻死,可以把BN收到CONV里面,完了需要的话再放出来。

2. pytorch0.4中的.data和.detach()的区别:参考链接,主要是说, 相同点有,都返回一个requires_grad=False的tensor,且都和原tensor共享内存,即一动全动.不同点: autograd不会跟踪.data的计算,但是会跟踪.detach().也就是说,对于tensor X, 用data方法改变了X的值(例如inplace),反向传播的时候并不知道改变了,还是把X的值直接来过来用,但是detach会让反向传播了解到X的值发生了改变,从而语法报错.

实验表明,大多数情况下data和detach结果都是一样的,只有少数情况用data后计算的梯度不对.

a = torch.rand(5, requires_grad = True)
out = a.tanh() # tanh和sigmod会报错,其他均不会报错,不管是引入其他变量的计算还是a**2等运算
c = out.detach()
c += 1
out.sum().backward()
#在CNN中经测试,两种方法也没明显差异,但是鉴于有些情况下data会出错,所以建议用detach而不是data.

3, pytorch中的backward, 当一个非标量的tensor T 调用backward方法时, 需要传入一个与T维度相等的权重tensor W.

x = torch.randn(3, requires_grad=True)
y = x * 2
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

output: tensor([0.2000, 2.0000, 0.0002])

参考链接1, 链接2

 

2019.04.20 交叉熵损失函数的简要实现

下面代码段表示a是网络输出的类别预测矩阵,4个anchor, 3个类别,b是每个anchor应该对应的类别,ce是交叉熵。

import torch 
a = torch.rand(4,3)
b = torch.tensor([0,1,2,0])
print('predict:',a)
print('class:',b)
import torch.nn.functional as F
ce = F.cross_entropy(a, b, size_average=False)
print ('CE:',ce)


output: 
predict: ([[0.3139, 0.3682, 0.2602],
          [0.0739, 0.5971, 0.2072],
          [0.6904, 0.3558, 0.7140],
          [0.5112, 0.8050, 0.5738]])
class: tensor([0,
               1,
               2,
               0,])
CE: 4.1292

下面的代码段是自己实现CE的过程,实际在计算softmax分数时,讲预测矩阵都减去其最大值得到新的预测矩阵,使得指数项值在0-1之间。其实并不需要计算出实际的softmax分数,按照下面的公式推算出即可。 

def log_sum_exp(x):
    x_max = x.data.max()
    return torch.log(torch.sum(torch.exp(x-x_max), 1, keepdim=True)) + x_max
torch.sum(log_sum_exp(a) - a.gather(1, b.view(-1, 1)))


output:
tensor(4.1292)

gather函数便是返回了xij的值,就是第i个anchor在正确类别j上的网络输出的预测值。参考链接

 

2019.5.12

smooth l1 loss

最近做一个项目涉及到loss的修改。在此总结一下smoothl1 loss的特点和设计类似于smoothl1回归损失函数的几个原则,

1.为什么用smoothl1? 

smoothl1是在faster rcnn中引入的。训练初期的outlier(离群点)多,loss较大,如果不加以限制,很容易grad过大,从而跑飞,所以适合用L1损失(grad==1)。训练中后期inlier与GT较为接近,如果grad仍然为常值不利于收敛到一个更好的值,此外对于不是outlier的样本,loss大的应该给予大的grad,loss小应该grad小,所以适合用L2损失。Libra RCNN提出的改进smoothl1,因为在训练过程中发现回归的梯度大部分被outlier主导了(因为他们的梯度等于max,常值1),所以将smoothl1的grad的线性部分改为log函数,增大与max的接近程度(log函数是凸函数),增大inlier的grad.

2.如何设计一个类似smoothl1的损失函数呢?

在引入reputlion loss到ssd里面时,有一项是需要对 所有正的anchors与和其第二大Iou的GT 之间的IOG进行约束,IOG的范围是[0,1],因此smoothl1不再适用,所以需要自己设计一个类似于smoothl1的距离度量函数,个人觉得主要有以下三点需要考虑:

  • 过原点。x=0时,loss=0毋庸置疑;
  • 是一个连续函数,也就是说两段函数在间断点的函数值相等,且需为单增函数,保证x越大,loss越大
  • 导函数第一段是一个增函数,第二段是一个常函数(保证outlier不会跑飞),且在连接处导数相等(否则函数 不连续,违反上一条规则)。
  • 以上均是对于x>0的情况进行讨论.当x<0时,若grad保持为正,那么则需要grad的图像是关于y轴对称,也就是减函数,例如Libra R-CNN;当x<0时,若grad保持为负,那么则需要grad的图像是关于原点对称,也就是增函数。如下式所示,当x<0,越是inlier,x越大,若grad>0,为减函数那么grad越小,因此w变化幅度越小,正确!;若grad<0,为增函数,那么grad越大但是其绝对值越小,例如偏差为-1比-10更加inliner,以smoothl1为例,grad同样为-1和-10,利用下式进行计算时,grad为-1时比-10时  w的变化幅度更小,正确!。

w = w - lr*grad

Pytorch相关操作

  • gather和scatter_.  gather是从一个现有的张量A中按照指定的索引取出一些值从而得到最终的张量,输出的shape和索引矩阵的shape相等。torch和tensor均具有gather方法。scatter_只有张量具有该方法,torch没有。表示对给定的张量A,按照索引将另一个同索引矩阵相同shape的赋值矩阵填到A中。

  • hook。用以variable的register_hook和用在module上的register_forward_hook和register_backward_hook。建议使用register_hook。register_forward_hook的grad_in和grad_out均是正确的,但是register_backward_hook的grad_in是不正确的,不是model的input的grad,而是module的最后一次操作的grad,但是它的grad_out是正确的。register_hook只返回变量的grad,不返回value。插hook的所有操作需要在将数据x传到net之前注册好,然后再backward()结束后删除句柄  (调用.remove)
  • module,children,state_dict, parameters等遍历model的方法比较。
  • collate_fn定义了如何取样本,dataloader按照dataset(或者自己重写的dataset)中的getitem方法获取img和target,然后按照一一对应的方式,将batch个一一打包成元组,格式:((img,target),(),……),但是常常我们需要自定义读取方式,比如img和target分开, 但可以对应上,就需要自己写collate_fn方法。此处可能会用到torch.stack方法,这个函数和torch.cat不同,后者不改变张量轴的个数,但是stack增加1个轴,可以给定增加的轴位于第几维,若给定0,则表示按照depth方向堆叠原tensor, tensor的内容不改变。

__setattr__等方法

# -*- coding:UTF-8 -*-
class test():
    p = 1
    def __init__(self, p1=0):
        self.p1 = p1

    def __setattr__(self, key, value):
        print('__setattr__')
        self.__dict__[key] = value

    def __getattr__(self, item):
        print('__getattr__')
        try:
            return self.__dict__[item]
        except KeyError:
            # raise AttributeError('test class not contain %s attribute'%(item))
            print('test class not contain %s attribute'%(item))

    def __setitem__(self, key, value):
        print('__setitem__')
        self.__dict__[key] = value

    def __getitem__(self, item):
        print('__getitem__')
        try:
            return self.__dict__[item]
        except KeyError:
            raise AttributeError('test class not contain %s attribute'%(item))

    def forward(self):
        return None

a = test() # self.p1 = p1 调用__setattr__
a.p1 # 在self.__dict__中查找到p1属性,因此不需要调用__getattr__
a['p3'] = 3 # 调用__setitem__
a['p3'] #调用__getitem__
a['p1'] # 调用__getitem__
print(test.__dict__.keys()) #类的__dict__包含了类的变量和方法
print(a.__dict__) #实例的__dict__包含了实例的变量,也就是self.xxx
a.p2 # 在self.__dict__中查找不到p2属性,因此调用__getattr__
getattr(a, 'p2') # 转化为a.p2,__dict__查找不到p2属性,因此调用__getattr__
getattr(a, 'p1') # 转化为a.p1,不需要调用__getattr__
setattr(a, 'p2', 1) # 转化为a.p2=1,进行__setattr__操作

运行结果为 

__setattr__
__setitem__
__getitem__
__getitem__
['__module__', '__getitem__', '__setattr__', '__getattr__', 'p', '__setitem__', 'forward', '__doc__', '__init__']
{'p3': 3, 'p1': 0}
__getattr__
test class not contain p2 attribute
__getattr__
test class not contain p2 attribute
__setattr__

总结:

1. 每个类都有__setattr__和__getattr__函数,一般不需要重写。因为__init__中常常需要对类的属性进行赋值,所以自带__setattr__。

2. 当查找一个实例的一个属性值时,会先在__dict__中查找,如若查找不到,则会调用__getattr__方法。因此__getattr__可以看成是查找属性时的最后一把锁。

3. __setitem__和__getitem__需要用户重写。是将类的实例化对象看成一个字典,然后添加属性和对应的值。不管是__setattr__还是__setitem__,一般都是往实例化对象的__dict__中添加键值。在__setattr__中,不能再用self.key = value,这样会导致无限制重复调用__setattr__函数本身,所以是在__dict__中添加键值。若是采用36行代码那样访问实例的属性,则是将类看成一个字典,则会直接调用__getitem__函数,而不会去__dict__中查找。

前三点小结:不管采用a.p3 = 1(__setattr__)还是a['p3'] = 1(__setitem__),本质上都是往__dict__中添加键值对。只不过__setitem__需要用户重写,__setattr__则可以直接使用而不用重写。在访问p3时,不管是a.p3(__dict__)还是a['p3'](__getitem__)都可以访问出p3属性的值。只不过__getitem__需要用户重写。

4. __dict__对于类来说,包含了类的变量(一般定义在__init__之外)和类的函数;对于实例化对象来说,则包含了实例化对象的变量,也就是self.xxx那些。

5. getattr先转化为a.xxx,然后再查找。先在__dict__中查找,找不到再执行__getattr__函数。对比41和42行代码可发现。

同理setattr也是转化为a.xxx = xx,然后利用__setattr__函数

ps: 在查找实例的属性值时,不一定会调用__getattr__, 但一定会调用__getattribute__函数。

再对比一下dir函数和__dict__的不同。

# -*- coding:UTF-8 -*-
class test():
    p = 1
    def __init__(self, p1=0):
        self.p1 = p1
        
a = test()
print(dir(a))
print(a.__dict__.keys())
print(dir(test))
print(test.__dict__.keys())

运行结果为

['__doc__', '__init__', '__module__', 'p', 'p1']
['p1']
['__doc__', '__init__', '__module__', 'p']
['p', '__module__', '__doc__', '__init__']

可以看到,不管是类还是实例化对象,dir()均列举出了其所有的函数和变量。而__dict__则不一样,对于类来说,其包含了类的变量和方法,对于实例化对象来说,只包含了该对象的变量,没有方法,就是self.xxx。换句话说,对于类来说,dir()和__dict__是一样的,对于实例化对象来说,dir()和__dict__不一样,dir()是类的__dict__和自身__dict__的并集。

最后,如果类有 __dir__() 方法,在使用dir()时其将会被调用:

除了上面介绍的魔术方法,常见的还有__call__(让类的实例化对象变成可调用对象,callable)__repr__(当print 类的实例化对象时,不想输出其内存地址,便可以通过该方法实现自定义输出内容)。

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
卡尔曼滤波器学习笔记) 在上一篇笔记中,我们介绍了卡尔曼滤波器的基本原理和数学模型。现在,让我们继续深入学习卡尔曼滤波器的实际应用。 1. 状态方程(State Equation): 卡尔曼滤波器的核心是状态方程,描述系统的状态如何随时间变化。通常表示为x(k+1) = A * x(k) + B * u(k) + w(k),其中x(k)表示系统在时刻k的状态,A是状态转移矩阵,u(k)表示输入,B是输入矩阵,w(k)表示过程噪声。 2. 观测方程(Measurement Equation): 观测方程描述了系统状态与观测值之间的关系。通常表示为z(k) = H * x(k) + v(k),其中z(k)表示观测值,H是观测矩阵,v(k)表示观测噪声。 3. 卡尔曼增益(Kalman Gain): 卡尔曼增益决定了如何将先验估计与观测值相结合,得到更准确的后验估计。卡尔曼增益的计算公式为K(k) = P(k|k-1) * H^T * (H * P(k|k-1) * H^T + R)^-1,其中P(k|k-1)表示先验误差协方差矩阵,R表示观测噪声的协方差矩阵。 4. 预测步骤(Prediction Step): 预测步骤用于根据系统的模型预测下一时刻的状态和误差协方差矩阵。预测步骤的计算公式为x(k+1|k) = A * x(k|k) + B * u(k),P(k+1|k) = A * P(k|k) * A^T + Q,其中x(k|k)表示先验估计,P(k|k)表示先验误差协方差矩阵,Q表示过程噪声的协方差矩阵。 5. 更新步骤(Update Step): 更新步骤用于根据观测值修正先验估计,得到后验估计和后验误差协方差矩阵。更新步骤的计算公式为x(k+1|k+1) = x(k+1|k) + K(k+1) * (z(k+1) - H * x(k+1|k)),P(k+1|k+1) = (I - K(k+1) * H) * P(k+1|k),其中x(k+1|k+1)表示后验估计,P(k+1|k+1)表示后验误差协方差矩阵,K(k+1)表示卡尔曼增益。 以上就是卡尔曼滤波器的基本概念和计算步骤。在实际应用中,我们需要根据具体问题来确定状态方程、观测方程和相关参数。卡尔曼滤波器的优势在于可以有效地处理噪声和不确定性,广泛应用于目标跟踪、导航、机器人等领域。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值