数据增强代码实践,补一点多进程,多线程的知识点

  • 目前最先进的神经网络模型,其本质上也是在利用一系列线性和非线性的函数去拟合目标输出。既然是拟合,当然越多的样本就能获得越准确的结果,这也是为什么现在训练神经网络所使用的数据规模越来越大。

  • 在实际使用中,我们往往可能只有几千甚至几百份数据。面对神经网络数以 M 计的参数,很容易陷入过拟合的陷阱。因为神经网络的收敛需要一个较长的训练过程,而这个过程中网络遇到的反反复复都是训练集的那几张图片,硬背都背下来了,自然很难学到什么能够泛化的特征。一个自然的想法是,能不能用一张图片去生成一系列图片,从而成百上千倍地扩充我们的数据集?而这,也正是数据增强的目的之一。

  • 神经网络是没有常识的,因此它永远只会用最“方便”的方式区分两个类别。假设我们要训练一个区分苹果和橘子的神经网络,但手上的数据只有红苹果和青橘子,那无论我们拍摄多少张照片,神经网络也只会简单地认为红色的就是苹果,青色的就是橘子。这在实际使用中经常出现,拍摄的灯光、拍摄的角度等等,任何一个不起眼的区分点,都会被神经网络当做分类的依据。

  • 数据增强的目标并不是无脑地堆数据,而是尽可能地去覆盖原始数据无法覆盖不到,但现实生活中会出现的情况。使用数据增强技术可以增加数据集中图像的多样性,从而提高模型的性能和泛化能力。在Pytorch框架中,常用的数据增强的函数主要集成在了transforms文件中,由于transforms的输入规定为PIL文件格式,所以我们需要使用PIL.Image模块。

  • 导包:

    • from PIL import Image
      from pathlib import Path
      import matplotlib.pyplot as plt
      import torch
      import numpy as np
      plt.rcParams["savefig.bbox"]="tight"
      org_img=Image.open(Path("nest.jpg"))
      torch.manual_seed(0)
      print(np.array(org_img).shape) 
      # (2286, 2603, 3)
      
    • resize,缩放

    • import torchvision.transforms as T
      resize_img=[T.Resize(size=newsize)(org_img) for newsize in [1000,2000]]
      
      ax1=plt.subplot(131)
      ax1.set_title("original")
      ax1.imshow(org_img)
      
      ax2=plt.subplot(132)
      ax2.set_title("1000*1000")
      ax2.imshow(resize_img[0])
      
      ax3=plt.subplot(133)
      ax3.set_title(2000*2000)
      ax3.imshow(resize_img[1])
      
      plt.show()
      
    • 在这里插入图片描述

    • 灰度化

    • gray_img = T.Grayscale()(org_img)
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('gray')
      ax2.imshow(gray_img,cmap='gray')
      plt.show()
      
    • 在这里插入图片描述

    • 标准化

    • norm_img=T.Normalize(mean=(0.5,0.5,0.5),std=(0.5,0.5,0.5))(T.ToTensor()(org_img))
      norm_img=[T.ToPILImage()(norm_img)]
      
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('normalize')
      ax2.imshow(norm_img[0])
      
      plt.show()
      
    • 在这里插入图片描述

    • 旋转

    • plt.rcParams['font.sans-serif'] = ['SimHei']
      rotate_img=[T.RandomRotation(degrees=180)(org_img)]
      # print(rotate_img)
      
      ax1=plt.subplot(121)
      ax1.set_title("original")
      ax1.imshow(org_img)
      
      ax2=plt.subplot(122)
      ax2.set_title("$180$")
      ax2.imshow(rotate_img[0])
      
      plt.show()
      
    • 在这里插入图片描述

    • 中心裁剪

    • center_crop=[T.CenterCrop(size=newsize)(org_img) for newsize in (300,600)]
      
      ax1 = plt.subplot(131)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(132)
      ax2.set_title('300*300')
      ax2.imshow(np.array(center_crop[0]))
      
      ax3 = plt.subplot(133)
      ax3.set_title('600*600')
      ax3.imshow(np.array(center_crop[1]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 随机裁剪

    • rand_corp=[T.RandomCrop(size=newsize)(org_img) for newsize in [500,1000]]
      
      ax1 = plt.subplot(131)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(132)
      ax2.set_title('500*500')
      ax2.imshow(np.array(rand_corp[0]))
      
      ax3 = plt.subplot(133)
      ax3.set_title('1000*1000')
      ax3.imshow(np.array(rand_corp[1]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 加入高斯噪声

    • blur_img=[T.GaussianBlur(kernel_size=(3,3),sigma=x)(org_img) for x in (30,60)]
      
      ax1 = plt.subplot(131)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(132)
      ax2.set_title('sigma=30')
      ax2.imshow(np.array(blur_img[0]))
      
      ax3 = plt.subplot(133)
      ax3.set_title('sigma=60')
      ax3.imshow(np.array(blur_img[1]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 调色,饱和等

    • colorjitter=[T.ColorJitter(brightness=(0.2,0.8),contrast=(0.5,0.5),saturation=(0.5,0.5),hue=0.5)(org_img)]
      # 亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)
      # brightness_factor从[max(0, 1 - brightness), 1 + brightness]中随机采样产生。应当是非负数。
      # contrast_factor从[max(0, 1 - contrast), 1 + contrast]中随机采样产生。应当是非负数。
      # saturation_factor从[max(0, 1 - saturation), 1 + saturation]中随机采样产生。应当是非负数。
      # hue_factor从[-hue, hue]中随机采样产生,其值应当满足0<= hue <= 0.5或-0.5 <= min <= max <= 0.5
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      ax2 = plt.subplot(122)
      ax2.set_title('colorjitter')
      ax2.imshow(np.array(colorjitter[0]))
      plt.show()
      
    • 在这里插入图片描述

    • 水平翻转

    • horizon=[T.RandomHorizontalFlip(p=1)(org_img)]
      # p表示概率
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('horizon')
      ax2.imshow(np.array(horizon[0]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 垂直翻转

    • vertical=[T.RandomVerticalFlip(p=1)(org_img)]
      
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('VerticalFlip')
      ax2.imshow(np.array(vertical[0]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 加入自定义噪声

    • def add_noise(imputs,noise_fac=0.5):
          noise=imputs + torch.randn_like(imputs)*noise_fac
          noise=torch.clip(noise,0.0,1.0)
          # clip这个函数将将数组中的元素限制在a_min, a_max之间,大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min。
          return noise
      
      noise_img=[add_noise(T.ToTensor()(org_img),fac) for fac in (0.4,0.8)]
      noise_img=[T.ToPILImage()(n_img) for n_img in noise_img]
      
      ax1 = plt.subplot(131)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(132)
      ax2.set_title('noise_factor=0.4')
      ax2.imshow(np.array(noise_img[0]))
      
      ax3 = plt.subplot(133)
      ax3.set_title('noise_factor=0.8')
      ax3.imshow(np.array(noise_img[1]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 加入掩码块

    • def add_box(img,num_box,size=100):
          h,w=size,size
          img=np.asarray(img).copy()
          img_size=img.shape[1]
          boxes=[]
          for k in range(num_box):
              y,x=np.random.randint(0,img_size-w,(2,))
              img[y:y+h,x:x+w]=0
              boxes.append((x,y,h,w))
          img=Image.fromarray(img.astype('uint8'),'RGB')
          return img
      
      block_img=[add_box(org_img,num_box=15)]
      
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('add black boxes')
      ax2.imshow(np.array(block_img[0]))
      
      plt.show()
      
    • 在这里插入图片描述

    • 中心掩码块

    • def add_center(o_img,size=150):
          h,w=size,size
          img=np.asarray(o_img).copy()
          img_size=img.shape[1]
          img[int(img_size/2-h):int(img_size/2+h),int(img_size/2-w):int(img_size/2+w)]=0
          img=Image.fromarray(img.astype('uint8'),'RGB')
          return img
      
      center_img=[add_center(org_img,size=200)]
      
      ax1 = plt.subplot(121)
      ax1.set_title('original')
      ax1.imshow(org_img)
      
      ax2 = plt.subplot(122)
      ax2.set_title('add_center')
      ax2.imshow(np.array(center_img[0]))
      
      plt.show()
      
    • 在这里插入图片描述

多进程与多线程

  • 并发:在一段时间内交替去执行多个任务。例子:对于单核cpu处理多任务,操作系统轮流让各个任务交替执行

  • 并行:在一段时间内真正的同时一起执行多个任务。例子:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的任务,多个内核是真正的一起同时执行多个任务。这里需要注意多核cpu是并行的执行多任务,始终有多个任务一起执行

  • 进程是操作系统分配资源的最小单元, 线程是操作系统调度的最小单元。(一个是供分配, 一个是供调度)

  • 一个应用程序至少包括1个进程,而1个进程包括1个或多个线程,线程的尺度更小。

  • 每个进程在执行过程中拥有独立的内存单元,而一个进程的多个线程在执行过程中共享内存。

  • 一座工厂(类似CPU),假设电力有限,只能供一个车间,即只能运行一个任务,里面有许多的车间(类似进程),执行单个的任务,这时,就是每次只能单个车间运行,若是另一个车间想工作,则当前车间得休息。

  • 而多核CPU,就像多个工厂,它可以同时让多个车间(类似多个进程)一起工作,当然,是工作在不同工厂里,也就是运行在不同CPU核上。

  • 而每个车间里有很多的工人,这就类似是线程,一个车间可以有多个工人,也就是一个进程可有多个线程。

  • 这就是基本的CPU、多核、进程、线程之间的关系。这里好像一切都很和谐,跟锁没什么关系。为社么会出现锁呢? 接下来就得谈谈内存占用的问题。

    • 在一个进程下,它拥有的资源是有限的(有多少呢,举个例子),而在这个进程下,多个线程是共享这些资源的,就像是一个车间地方大小是一定的,每个工人工作占用的地方不一致,那怎么办呢,那就得加把锁了,当某个工作地方被占满了就得挂上锁,告诉其他工人,这里已经容不下人了。得等人出来,也就是某个线程结束释放内存时,才能又进去人,开始新的线程。

    • 这里大佬们发明了一个简单防止冲突的方法,叫做互斥锁(Mutual exclusion,缩写 Mutex)。也就是说,一个线程在使用共享内存时,会把门锁住,防止其他人进来占用。后来的人得等着,等这把锁被解开,也就是空间被释放,才能再进去用这个空间。

    • 还有些房间,可以同时容纳n个人。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。

  • 多进程CPU会自动分配资源,同时可以跑在不同的内核上。而Python多线程则在CPU上实际上是间断的工作,就是一个线程跑,则其他线程处于休息状态,下一个线程开始,则其他线程又进入休息状态。

  • 多线程: threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴等待IO完成

    • 多线程Thread ( threading) 优点:相比进程,更轻量级.占用资源少。缺点:相比讲程,多线程只能并发执行,不能利用多CPU ( GIL)·相比协程:启动数目有限制,占用内存资源,有线程切换开销。适用于:Io密集型计算、同时运行的任务数目要求不多
  • 多进程:multiprocessing,利用多核CPU的能力,真正的并行执行任务

    • 多进程Process ( multiprocessing)·优点:可以利用多核CPU并行运算。缺点:占用资源最多、可启动数自比线程少,适用于:CPU密集型计算
  • 异步IO: asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行

    • 多协程Coroutine ( asyncio ) 优点:内存开销最少、启动协程数量最多。 缺点:支持的库有限制(aiohttp vs requests)、代码实现复杂。适用于:IO密集型计算、需要超多任务运行、但有现成库支持的场景
  • 一个进程里可有多个线程,一个线程里可有多个协程

  • 使用Lock对资源加锁,防止冲突访问

  • 使用Queue实现不同线程/进程之间的数据通信,实现生产者-消费者模式

  • 使用线程池Pool/进程池Pool,简化线程/进程的任务提交、等待结束、获取结果

  • 使用subprocess启动外部程序的进程,并进行输入输出交互

  • CPU密集型也叫计算密集型,是指 I/O 在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率相当高

    • 例如:压缩解压缩、加密解密、正则表达式搜索
  • I/O 密集型指的是系统运作大部分的状况是CPU在等I/O(硬盘/内存)的读/写操作,CPU占用率仍然较低。

    • 例如:文件处理程序、网络爬虫程序、读写数据库程序
  • 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。GIL简化了共享资源的管理.

    • 因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
  • 多线程threading机制依然是有用的,用于IO密集型计算。因为在I/O (read,write,send,recv,etc.)期间,线程会释放GIL,实现CPU和IO的并行因此多线程用于IO密集型计算依然可以大幅提升速度。但是多线程用于CPU密集型计算时,只会更加拖慢速度

  • 使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势。为了应对GIL的问题,Python提供了multiprocessing

  • 在这里插入图片描述

  • 新建线程系统需要分配资源、终止线程系统需要回收资源如果可以重用线程,则可以减去新建/终止的开销。一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。

  • 在这里插入图片描述

  • 使用线程池的好处1、提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源;2、适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短;3、防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题;4、代码优势:使用线程池的语法比自己新建线程执行线程更加简洁

  • 使用线程池:由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。线程池基本原理: 我们把任务放进队列中去,然后开N个线程,每个线程都去队列中取一个任务,执行完了之后告诉系统说我执行完了,然后接着去队列中取下一个任务,直至队列中所有任务取空,退出线程。

  • 在这里插入图片描述

  • # 创建队列实例, 用于存储任务
    queue = Queue()
    # 定义需要线程池执行的任务
    def do_job():
        while True:
            i = queue.get()
            time.sleep(1)
            print 'index %s, curent: %s' % (i, threading.current_thread())
            queue.task_done()
    if __name__ == '__main__':
        # 创建包括3个线程的线程池
        for i in range(3):
            t = Thread(target=do_job)
            t.daemon=True # 设置线程daemon  主线程退出,daemon线程也会推出,即时正在运行
            t.start()
        # 模拟创建线程池3秒后塞进10个任务到队列
        time.sleep(3)
        for i in range(10):
            queue.put(i)
        queue.join()
    
  • 多线程和多进程在python中的支持

  • 在这里插入图片描述

  • 信号量(英语:Semaphore)又称为信号量、旗语,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待( wait)时,该计数值减一;·当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态. semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.

  • 设置主进程守护:子进程对象.daemon=true。多线程是Python程序中实现多任务的一种方式;线程是程序执行的最小单位;同属一个进程的多个线程共享进程所拥有的全部资源.

猜你喜欢

转载自blog.csdn.net/weixin_43424450/article/details/133269326