【学习记录】用pytorch自己写数据生成器

学习过程中的记录和思考,有问题或不严谨的地方还请指出

1.self是啥

每天都在定义类,每天都在写self,这到底是什么呢
比如我们定义了一个类

class Solution():
    def nit(self,a):
        self.a=a
    def one(self):
        self.a+=1
        print(self.a)

self在这里类似于C++的指针,有种定位的意思,当我们对这个类进行实例化,调用该对象的方法,比如solution=Solution()将Solution这个类实例化为对象
现在我想调用该对象中的one方法,对象就会将自身的引用作为第一个参数传给该方法,那么python就知道该去处理哪一个对象的方法。
我的理解是:

solution=Solution()

solution.nit(2)

这里的solution就已经把自己引用成了self传输了进去,所以不再需要传参,同理,如果想调用对象的one方法,直接solution.one()就好了,不再需要传输参数

2. __init__方法

这个方法很熟悉了,一般在定义一个类时需要初始化 就会重写__init__()方法

3.定制序列\定制容器

也就是说,在类中定义了以下方法后,就可以像在列表字符串中一样使用一些方法,提高使用效率。

方法作用__getitem__方法定义获取容器中指定元素的行为,这样就可以通过对象+key直接访问

__setitem__方法(self,key,value)定义设置容器中指定元素的行为,相当于

,可以直接来给指定索引位置赋值__len__方法定义了当被

函数调用时候的行为(返回容器中元素的个数),也就是可以对这样对象做len(object)的操作了

4.描述符(property原理)

方法作用/含义. (例如solution.x)通过点操作符访问对象的属性_

()方法用于访问属性,它返回属性的值,当访问对象的属性时,该方法会自动调用。第一个参数:这个描述符的拥有者所在的类的实例,第二个参数是描述符的拥有者所在的类本身

()方法将在属性分配操作中调用,不返回任何内容;对对象的属性进行赋值操作的时候,会自动调用该方法

()方法删除操作,无返回内容

(self,name)当用户试图获取一个不存在的属性时的行为

(self,name)当该类的属性被访问时的行为

(self,name,value)定义后属性可以被设置

(self,name)定义后可以通过该方法删除属性

_以字典的形式显示出当前对象的所有属性以及相对应的值

https://blog.csdn.net/qq_37718687/article/details/123877438?spm=1001.2014.3001.5502

5.迭代器: _iter ()和 _ _next _()

可迭代对象:提供迭代方法的容器成为可迭代对象,通常接触的可迭代对象有序列(如列表、元组、字符串)、字典等,都支持迭代操作,直观理解就是:你都可以通过for循环访问其中的每一个元素

nums=[1,2,3,4,5]
for num in nums:
    print(num)

关于迭代,Python提供了 iter() 和 next() ,对一个可迭代对象调用 iter() 就可以得到它的迭代器,调用 next() 迭代器就会返回下一个值。

def __iter__(self)
    return self
class Solution():
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def __iter__(self):
        return self
    def __next__(self):
        self.a+=1
        if self.a>self.b:
            raise StopIteration
        return self.a
solution=Solution(1,10)
for solu in solution:
    print(solu)

so=iter(solution)
next(so)
next(so)

6.生成器

参考‘小甲鱼’

首先什么是生成器呢?

https://fishc.com.cn/thread-56023-1-1.html

一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用 yield 关键字而不是 return。如果一个 def 的主体包含 yield,这个函数会自动变成一个生成器(即使它包含一个 return)。
每当生成器被调用的时候,它会返回一个值给调用者。然后在生成器内部使用yield来完成。除了以上内容,创建一个生成器没有什么多余步骤了。举例来说:


def function1():
    return 1

def function2():
    yield 1

上面的function2就是生成器函数,生成器函数会返回生成器的迭代器(就是生成器,很绕吧,再来一遍, 生成器的迭代器==生成器)。

  • 使用yield的主要目的是为了边用边生成,当一个生成器函数调用yield,生成器函数的”状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用 next()。一旦next() 再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用 next(),yield 保存的状态就被无视了。
def function(a):
    print("第1天在好好学习!")
    yield a

    yield a+1
    print("第2天在好好学习!")
    b=yield a+2
    print("第3天在好好学习!",b)
    yield b

f=function(1)

next(f())

next(f())

next(f())

比较特殊的是:用普通小括号括起来的推导式就是生成器表达式,可以用next()来进行迭代

(i for i in range(10))

copy的一些笔记

generator 是用来产生一系列值的
yield 则像是 generator 函数的返回结果
yield 唯一所做的另一件事就是保存一个 generator 函数的状态
generator 就是一个特殊类型的迭代器(iterator)
和迭代器相似,我们可以通过使用 next() 来从 generator 中获取下一个值
通过隐式地调用 next() 来忽略一些值

8. __call__方法

如果类中定义了__call__()方法,那么该类的实例对象也将成为可调用对象。该对象被调用时,将执行__call__()方法中的代码。该方法相当于’()’这个运算符。比如:

class Solution:
    def __call__(self,a):
        print("代码快进步",a)
solu=Solution()
solu("好的!")

这样solu实例对象就变成了可调用对象。

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。

也就是说,()==_ _call__()

1.实现dataset

  • transform定义:(假设输入图像是RGB格式的)首先是用torchvision中的transforms包对图像输入进行预处理,transform这个函数把接收到的图像首先缩放到256 × 256 × 3 256\times 256\times 3 2 5 6 ×2 5 6 ×3,然后随机截取成224 × 224 × 3 224\times224\times3 2 2 4 ×2 2 4 ×3,然后转化成tensor格式,totensor会把像素归一化到[0,1]范围内,至于后面的标准化(transforms.Normalize),这篇文章讲的很清楚。

https://zhuanlan.zhihu.com/p/476297637

  • 思考1:Resize()和RandomCrop()有什么区别,具体是怎么操作的【详细过程见Resize()和RandomCrop()的源码】
    假设我们输入的图片大小是(height,weight)

transforms方法具体作用和实现Resize()缩放,图像的长宽比变换前后没有变化,在下面的例子中,我们输入Resize((256,256)),那么图像会直接缩放成256*256,如果我们只输入一个树,Resize(256),那么resize会找到较短的那个边(比如height>width),把width变成256,然后对height等比缩放,最后变为(

,size)RandomCrop()随机裁剪,还是下面的代码,RandomCrop()首先会生成两个数

,这两个数分别在

之间,作为裁剪后图片不同方向的下届,最后裁剪出来的图片就是i~i+224, j~j+224CentorCrop()同RandomCrop(),只是 i 和 j 不再随机生成

  • 又有一个问题,都是变化,它们有什么不同呢,假设原图像是300 × 300 300 \times 300 3 0 0 ×3 0 0,缩放后成了256 × 256 256 \times 256 2 5 6 ×2 5 6,这其中去掉的44个像素是怎么没有的呢?是使用了一些插值方法,具体就不说了,又是很琐碎的一堆知识。相比之下,裁剪和它的名字一样,就是把多余的部分直接剪掉,把i~i+224, j~j+224之外的像素直接丢掉就好了。
    更多的方法细节见源码,源码真的很清楚。
from PIL.Image import Image
from torch.utils.data import Dataset
from torchvision.transforms import transforms

transform=transforms.Compose([transforms.Resize((256,256)),
                              transforms.RandomCrop(224),
                              transforms.ToTensor(),
                              transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])])

def default_loader(path):
    return Image.open(path).convert('RGB')

class MyDataset(Dataset):
    def __init__(self,images,labels,loader=default_loader,transform=None):
        self.images=images
        self.labels=labels
        self.loader=loader
        self.transform=transform

    def __getitem__(self, index):
        img,target=self.images[index],self.labels[index]
        img=self.loader(img)
        if self.transform is not None:
            img=self.transform(img)
        return img,target

    def __len__(self):
        return len(self.images)

写完MyDataset,接下来就可以直接使用pytorch中的dataloader了

from torch.utils.data import DataLoader

train_loader=DataLoader(MyDataset(train_list,train_labels,transform=centre_crop),
batch_size=batch_size,shuffle=True,num_workers=8)

2.DataLoader

为什么写成上面的Mydataset的形式就可以直接使用DataLoder了呢,这就又要说到DataLoader的原理了(两行搞定的代码原理怎么这么复杂这么复杂)
首先MyDataset()的输出:是索引i n d e x index i n d e x的i m g , t a r g e t img,target i m g ,t a r g e t
接下来看DataLoader的init中比较重要的几个部分:

    def __init__(self, dataset, batch_size=1, shuffle=False, sampler=None,
                 batch_sampler=None, num_workers=0, collate_fn=None,
                 pin_memory=False, drop_last=False, timeout=0,
                 worker_init_fn=None, multiprocessing_context=None):
        torch._C._log_api_usage_once("python.data_loader")
        self.dataset = dataset

        if sampler is None:
            if self._dataset_kind == _DatasetKind.Iterable:

                sampler = _InfiniteConstantSampler()
            else:
                if shuffle:
                    sampler = RandomSampler(dataset)
                else:
                    sampler = SequentialSampler(dataset)
        if batch_size is not None and batch_sampler is None:

            batch_sampler = BatchSampler(sampler, batch_size, drop_last)

        self.batch_size = batch_size
        self.drop_last = drop_last
        self.sampler = sampler
        self.batch_sampler = batch_sampler

有三个部分是对数据的shuffle和batch处理

方法作用\含义RandomSampler随机采样来返回DataSet的索引位置 ,返回:

SequentialSampler顺序采样来返回DataSet的索引位置 ,返回:

BatchSampler当达到一个batch的容量,就会被yield出去,假设batch为3,返回:

  • 看一下BatchSampler中__iter__(self)的代码
ef __iter__(self):
        batch = []
        for idx in self.sampler:
            batch.append(idx)
            if len(batch) == self.batch_size:
                yield batch
                batch = []
        if len(batch) > 0 and not self.drop_last:
            yield batch

    def __iter__(self):
        if self.num_workers == 0:
            return _SingleProcessDataLoaderIter(self)
        else:
            return _MultiProcessingDataLoaderIter(self)
    def __next__(self):
        if self.num_workers == 0:
            indices = next(self.sample_iter)
            batch = self.collate_fn([self.dataset[i] for i in indices])
            if self.pin_memory:
                batch = pin_memory_batch(batch)
            return batch

大致思路就是,随机生成数据长度大小的索引数,把读取进来的数据按照前面的索引放入一个又一个的batch中
上面没有考虑num_works>0的情况,有关于多进程的部分感觉这篇文章讲的很仔细

https://blog.csdn.net/g11d111/article/details/81504637

创建了这个DataLoader对象后,就可以循环这个对象加载到模型中训练了

3.在一个类中实现

’来自一位优秀的同门’
以下直接for循环就可以实现数据以batch的形式传入模型

class DataLoader(object):
    def __init__(self,X,y,batch_size):
        self.X = X
        self.y = y
        self.length = len(y)
        self.arr = np.array(range(self.length))
        self.batch_size = batch_size

    def __iter__(self):
        self.num = 0
        self.seq = np.random.permutation(self.arr)
        return self

    def __next__(self):
        if self.num+self.batch_size  self.length:
            sample = self.seq[self.num:(self.num+self.batch_size)]
            self.image = self.X[sample]
            self.label = self.y[sample]
            self.num += self.batch_size
            return self.image, self.label
        else:
            raise StopIteration

    def __len__(self):
        return len(self.y)

有用还请点赞哦

平时很简单几行的代码内在逻辑和实现细节真的是琐碎啊,道阻且长,加油喽!

Original: https://blog.csdn.net/weixin_57749806/article/details/124264608
Author: syusukeJ
Title: 【学习记录】用pytorch自己写数据生成器

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/710435/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球