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