maskrcnn_benchmark理解记录——transforms.py。__init__和__call__和类、继承、对象、实例、方法和数据增强

文件地址:maskrcnn-keypoint/maskrcnn_benchmark/data/transforms/transforms.py
这个文件我看了好久。
此函数主要是用于数据预处理、增强的,如resize,水平翻转RandomHorizontalFlip,数据转换ToTensor,归一化Normalize。是调用了torchvision.transforms.[地址]
这个放在最后,主要记录下__init__和__call__以及这里怎么用的。

    一、__init__和__call__.

参考:【1】,【2】、【3】

在Python中,函数其实是一个对象,由于 f 可以被调用,所以f被称为可调用对象。所有的函数都是可调用对象。:

>>> f = abs 
>>> f.__name__ 
'abs' 
>>> f(-123) 123
利用__call__()可以把一个类实例变成一个可调用对象。如把 Person 类变成一个可调用对象:
class Person(object):#(object)表示该类是从哪个类继承下来的通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的.
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def say(self):
        return("I am %s" % (self.name))

    def __call__(self, friend):
        print('My name is %s...' % self.name)
        print('My friend is %s...' % friend)

现在可以对 Person 实例直接调用:

>>> pp=Person()
>>> pp
<__main__.Person object at 0x10a67a590>
>>> p = Person('Bob', 'male')   #定义了一个类实例,传入类的参数‘name,gender’
>>> p('Tim')  #'Tim'是类实例的参数。
My name is Bob...
My friend is Tim...

上面 p('Tim')相当于实例(可以把这个实例看成一个函数)调用了__call__()方法。可以写成Person('Bob', 'male')('Tim')
单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

【【关于类、继承、对象、实例、方法。说明。

1.pp和Person是一个类,

2.(object)表示该类是从哪个类继承下来的通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的.

<__main__.Person object at 0x10a67a590>

pp指向的是一个Person的object,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Person本身则是一个类。可以自由地给一个实例变量绑定属性,即实例。

3.self代表一个对象。__init__方法的第一个参数永远是self,表示创建//指向的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self

4.def say()的话,代表方法。

5.p 和Person('Bob', 'male')代表一个实例,在创建实例的时候,不能传入空参数,必须传入与__init__方法匹配的参数。

对象一个模板,而实例就是这个模板的具体实现,即,self包含 name, gender和say()的属性和方法,而p包含name, gende分别为 'Bob', 'male'以及say()方法。】】

再举个__init__和__call__.例子

class Flower(object):
    def __init__(self,color):
        self.color = color
        print('color is', color)
    def __call__(self,dollar):
        self.dollar = dollar
        print('value is', dollar, 'dollar')

value = Flower('red')(100)

 该程序创建了一个Flower类,Flower('red')定义了一个实例,Flower('red')(100)相当于实例(可以把这个实例看成一个函数)调用了__call__()方法

二:transforms.py里面的

class Compose(object):#(object)
    def __init__(self, transforms):
        self.transforms = transforms

    def __call__(self, image, target): # target是action要操作的目标,image是当前的上下文环境,再结合transforms
        for t in self.transforms:
            image, target = t(image, target)
        return image, target

    def __repr__(self): ## 内置的方法 让 打印的对象 丰富起来
        format_string = self.__class__.__name__ + "("
        for t in self.transforms:
            format_string += "\n"
            format_string += "    {0}".format(t)
        format_string += "\n)"
        return format_string

class RandomHorizontalFlip(object):
    def __init__(self, prob=0.5):
        self.prob = prob

    def __call__(self, image, target):
        if random.random() < self.prob:
            image = F.hflip(image)
            target = target.transpose(0)  #翻转、resize这种ground-truth是同步变化的
        return image, target

class ToTensor(object):
    def __call__(self, image, target):
        return F.to_tensor(image), target

1.target大概是因为你翻转、归一化之后、ground-truth也需要变化.为了统一,所有类的参数和返回值都是image, target。是共用的。
2.调用时候(/maskrcnn_benchmark/).

/tools/train_net.py:make_data_loader→/data/build.py:build_transforms→data/transforms/build.py→build_transforms
from . import transforms as T
transform = T.Compose(
            [
                T.Resize(min_size, max_size),
                T.RandomHorizontalFlip(flip_prob),
                T.ToTensor(),
                normalize_transform,
            ])

T.Compose([])定义了一个实例transform,传给transforms的参数是列表[],也就是transforms=[ ]

再看这里:

(第一句)    for t in self.transforms:
(第二句)        image, target = t(image, target)
(第三句)    return image, target

1)假设编上序号,那么上面两句其实有:

t0= T.Resize(min_size, max_size),
t1= T.RandomHorizontalFlip(flip_prob),
t2= T.ToTensor(),
t3= normalize_transform,

t0(image, target)   → Resize(min_size, max_size)(image, target) 
t1(image, target)   →RandomHorizontalFlip(flip_prob)(image, target)
t2(image, target)
t3(image, target)

eg: t1= T.RandomHorizontalFlip(flip_prob),调用了类RandomHorizontalFlip,传入参数flip_prob,构成类实例t1,

然后t1(image, target)调用了__call__()方法。RandomHorizontalFlip(flip_prob)(image, target).

2)第三句return image, target 返回的其实是四组计算结果的值。


1、__int__()的作用是初始化某个类的一个实例
2、__call__()的作用是使实例能够像函数一样被调用,同时不影响实例本身的生命周期(__call__()不影响一个实例的构造和析构)。但是__call__()可以来改变实例的内部成员的值。

1.实例调用__class__属性时会指向该实例对应的类,然后可以再去调用其它类属性,毕竟类属性还是由类调用会比较好
example:
   self.__classs__.__name__    %首先用__class__将self实例变量指向类,然后再去调用__name__类属性,
   通常情况__name__的值为‘__main__’。

三、数据增强

参考https://www.jianshu.com/p/1ae863c1e66d
https://github.com/pytorch/vision/blob/master/torchvision/transforms/transforms.py

__all__ = ["Compose", "ToTensor", "ToPILImage", "Normalize", "Resize",
"Scale", "CenterCrop", "Pad", "Lambda", "RandomCrop", 
"RandomHorizontalFlip", "RandomVerticalFlip", "RandomResizedCrop", 
"RandomSizedCrop", "FiveCrop", "TenCrop","LinearTransformation", 
"ColorJitter", "RandomRotation", "Grayscale", "RandomGrayscale"]

ToTensor:Convert a PIL Image or numpy.ndarray to tensor.强调的是在做数据归一化之前必须要把PIL
Image转成Tensor,而其他resize或crop操作则不需要。
ToPILImage顾名思义是从Tensor到PIL Image的过程,和前面ToTensor类的相反的操作。
Resize:是对PIL Image做resize操作的。这里输入可以是int,此时表示将输入图像的短边resize到这个int数,
长边则根据对应比例调整,图像的长宽比不变。还有force resize
CenterCrop:是以输入图的中心点为中心点做指定size的crop操作,一般数据增强不会采用这个。
RandomCrop更常用,差别就在于crop时的中心点坐标是随机的,并不是输入图像的中心点坐标,因此基本上每次crop生成的图像都是有差异的。就是通过
i = random.randint(0, h - th)和 j = random.randint(0, w -tw)两行生成一个随机中心点的横纵坐标。注意到在__call__中最后是调用了
F.crop(img,i, j, h, w)来完成crop操作。
RandomHorizontalFlip类也是比较常用的,是随机的图像水平翻转,通俗讲就是图像的左右对调。从该类中的__call__方法可以看出水平翻转的概率是0.5。
RandomVerticalFlip类是随机的图像竖直翻转,通俗讲就是图像的上下对调。
假如我采用了crop操作,有100个图片,那么训练的时候是不是相当于有200张图片。数据集会增加吗?
回复不是的,如果你是CenterCrop操作因为这是一个确定的操作,就是每个图片在输进模型的时候都进行中间裁剪,不论多少个epoch还是相当于在训练这100个图片,
如果是0.5概率的randomCrop操作,因为这个随机属性的存在每个epoch拿到的训练数据都不尽相同(有的epoch进行了裁剪,有的epoch没有进行裁剪,
但是一个epoch的训练数据都是100个),在多个epoch以后相当于你用200张图片在训练,达到了数据增强的目的。
如果是FiveCrop 和TenCrop操作又和上面的两种crop机制不一样,这两个才是是直接增加每个epoch的数据,将每个epoch中数据增加为了500张和1000张。

 

展开阅读全文

没有更多推荐了,返回首页