浅谈keras通过model.fit_generator训练模型(节省内存)


Posted in Python onJune 17, 2020

前言

前段时间在训练模型的时候,发现当训练集的数量过大,并且输入的图片维度过大时,很容易就超内存了,举个简单例子,如果我们有20000个样本,输入图片的维度是224x224x3,用float32存储,那么如果我们一次性将全部数据载入内存的话,总共就需要20000x224x224x3x32bit/8=11.2GB 这么大的内存,所以如果一次性要加载全部数据集的话是需要很大内存的。

如果我们直接用keras的fit函数来训练模型的话,是需要传入全部训练数据,但是好在提供了fit_generator,可以分批次的读取数据,节省了我们的内存,我们唯一要做的就是实现一个生成器(generator)。

1.fit_generator函数简介

fit_generator(generator, 
steps_per_epoch=None, 
epochs=1, 
verbose=1, 
callbacks=None, 
validation_data=None, 
validation_steps=None, 
class_weight=None, 
max_queue_size=10, 
workers=1, 
use_multiprocessing=False, 
shuffle=True, 
initial_epoch=0)

参数:

generator:一个生成器,或者一个 Sequence (keras.utils.Sequence) 对象的实例。这是我们实现的重点,后面会着介绍生成器和sequence的两种实现方式。

steps_per_epoch:这个是我们在每个epoch中需要执行多少次生成器来生产数据,fit_generator函数没有batch_size这个参数,是通过steps_per_epoch来实现的,每次生产的数据就是一个batch,因此steps_per_epoch的值我们通过会设为(样本数/batch_size)。如果我们的generator是sequence类型,那么这个参数是可选的,默认使用len(generator) 。

epochs:即我们训练的迭代次数。

verbose:0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行

callbacks:在训练时调用的一系列回调函数。

validation_data:和我们的generator类似,只是这个使用于验证的,不参与训练。

validation_steps:和前面的steps_per_epoch类似。

class_weight:可选的将类索引(整数)映射到权重(浮点)值的字典,用于加权损失函数(仅在训练期间)。 这可以用来告诉模型「更多地关注」来自代表性不足的类的样本。(感觉这个参数用的比较少)

max_queue_size:整数。生成器队列的最大尺寸。默认为10.

workers:整数。使用的最大进程数量,如果使用基于进程的多线程。 如未指定,workers 将默认为 1。如果为 0,将在主线程上执行生成器。

use_multiprocessing:布尔值。如果 True,则使用基于进程的多线程。默认为False。

shuffle:是否在每轮迭代之前打乱 batch 的顺序。 只能与Sequence(keras.utils.Sequence) 实例同用。

initial_epoch: 开始训练的轮次(有助于恢复之前的训练)

2.generator实现

2.1生成器的实现方式

样例代码:

import keras
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
from sklearn.model_selection import train_test_split
from PIL import Image

def process_x(path):
 img = Image.open(path)
 img = img.resize((96,96))
 img = img.convert('RGB')
 img = np.array(img)

 img = np.asarray(img, np.float32) / 255.0
 #也可以进行进行一些数据数据增强的处理
 return img

count =1
def generate_arrays_from_file(x_y):
 #x_y 是我们的训练集包括标签,每一行的第一个是我们的图片路径,后面的是我们的独热化后的标签

 global count
 batch_size = 8
 while 1:
  batch_x = x_y[(count - 1) * batch_size:count * batch_size, 0]
  batch_y = x_y[(count - 1) * batch_size:count * batch_size, 1:]

  batch_x = np.array([process_x(img_path) for img_path in batch_x])
  batch_y = np.array(batch_y).astype(np.float32)
  print("count:"+str(count))
  count = count+1
  yield (batch_x, batch_y)

model = Sequential()
model.add(Dense(units=1000, activation='relu', input_dim=2))
model.add(Dense(units=2, activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='sgd',metrics=['accuracy'])

x_y = []
model.fit_generator(generate_arrays_from_file(x_y),steps_per_epoch=10, epochs=2,max_queue_size=1,workers=1)

在理解上面代码之前我们需要首先了解yield的用法。

yield关键字:

我们先通过一个例子看一下yield的用法:

def foo():
 print("starting...")
 while True:
  res = yield 4
  print("res:",res)
g = foo()
print(next(g))
print("----------")
print(next(g))

运行结果:

starting...
4
----------
res: None
4

带yield的函数是一个生成器,而不是一个函数。因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器的实例,当我们第一次调用next函数的时候,foo函数才开始行,首先先执行foo函数中的print方法,然后进入while循环,循环执行到yield时,yield其实相当于return,函数返回4,程序停止。所以我们第一次调用next(g)的输出结果是前面两行。

然后当我们再次调用next(g)时,这个时候是从上一次停止的地方继续执行,也就是要执行res的赋值操作,因为4已经在上一次执行被return了,随意赋值res为None,然后执行print(“res:”,res)打印res: None,再次循环到yield返回4,程序停止。

所以yield关键字的作用就是我们能够从上一次程序停止的地方继续执行,这样我们用作生成器的时候,就避免一次性读入数据造成内存不足的情况。

现在看到上面的示例代码:

generate_arrays_from_file函数就是我们的生成器,每次循环读取一个batch大小的数据,然后处理数据,并返回。x_y是我们的把路径和标签合并后的训练集,类似于如下形式:

['data/img\\fimg_4092.jpg' '0' '1' '0' '0' '0' ]

至于格式不一定要这样,可以是自己的格式,至于怎么处理,根于自己的格式,在process_x进行处理,这里因为是存放的图片路径,所以在process_x函数的主要作用就是读取图片并进行归一化等操作,也可以在这里定义自己需要进行的操作,例如对图像进行实时数据增强。

2.2使用Sequence实现generator

示例代码:

class BaseSequence(Sequence):
 """
 基础的数据流生成器,每次迭代返回一个batch
 BaseSequence可直接用于fit_generator的generator参数
 fit_generator会将BaseSequence再次封装为一个多进程的数据流生成器
 而且能保证在多进程下的一个epoch中不会重复取相同的样本
 """
 def __init__(self, img_paths, labels, batch_size, img_size):
  #np.hstack在水平方向上平铺
  self.x_y = np.hstack((np.array(img_paths).reshape(len(img_paths), 1), np.array(labels)))
  self.batch_size = batch_size
  self.img_size = img_size

 def __len__(self):
  #math.ceil表示向上取整
  #调用len(BaseSequence)时返回,返回的是每个epoch我们需要读取数据的次数
  return math.ceil(len(self.x_y) / self.batch_size)

 def preprocess_img(self, img_path):

  img = Image.open(img_path)
  resize_scale = self.img_size[0] / max(img.size[:2])
  img = img.resize((self.img_size[0], self.img_size[0]))
  img = img.convert('RGB')
  img = np.array(img)

  # 数据归一化
  img = np.asarray(img, np.float32) / 255.0
  return img

 def __getitem__(self, idx):
  batch_x = self.x_y[idx * self.batch_size: (idx + 1) * self.batch_size, 0]
  batch_y = self.x_y[idx * self.batch_size: (idx + 1) * self.batch_size, 1:]
  batch_x = np.array([self.preprocess_img(img_path) for img_path in batch_x])
  batch_y = np.array(batch_y).astype(np.float32)
  print(batch_x.shape)
  return batch_x, batch_y
 #重写的父类Sequence中的on_epoch_end方法,在每次迭代完后调用。
 def on_epoch_end(self):
  #每次迭代后重新打乱训练集数据
  np.random.shuffle(self.x_y)

在上面代码中,__len __和__getitem __,是我们重写的魔法方法,__len __是当我们调用len(BaseSequence)函数时调用,这里我们返回(样本总量/batch_size),供我们传入fit_generator中的steps_per_epoch参数;__getitem __可以让对象实现迭代功能,这样在将BaseSequence的对象传入fit_generator中后,不断执行generator就可循环的读取数据了。

举个例子说明一下getitem的作用:

class Animal:
 def __init__(self, animal_list):
  self.animals_name = animal_list

 def __getitem__(self, index):
  return self.animals_name[index]

animals = Animal(["dog","cat","fish"])
for animal in animals:
 print(animal)

输出结果:

dog
cat
fish

并且使用Sequence类可以保证在多进程的情况下,每个epoch中的样本只会被训练一次。

以上这篇浅谈keras通过model.fit_generator训练模型(节省内存)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python类和继承用法实例
Jul 07 Python
Windows上配置Emacs来开发Python及用Python扩展Emacs
Nov 20 Python
python使用opencv读取图片的实例
Aug 17 Python
深入学习Python中的上下文管理器与else块
Aug 27 Python
python RabbitMQ 使用详细介绍(小结)
Nov 08 Python
利用Python实现Shp格式向GeoJSON的转换方法
Jul 09 Python
python程序中的线程操作 concurrent模块使用详解
Sep 23 Python
python2 对excel表格操作完整示例
Feb 23 Python
django实现HttpResponse返回json数据为中文
Mar 27 Python
解析python 中/ 和 % 和 //(地板除)
Jun 28 Python
python 根据列表批量下载网易云音乐的免费音乐
Dec 03 Python
matplotlib事件处理基础(事件绑定、事件属性)
Feb 03 Python
python用什么编辑器进行项目开发
Jun 17 #Python
在keras中model.fit_generator()和model.fit()的区别说明
Jun 17 #Python
python语言的优势是什么
Jun 17 #Python
python有几个版本
Jun 17 #Python
python实例化对象的具体方法
Jun 17 #Python
python和php学习哪个更有发展
Jun 17 #Python
python中线程和进程有何区别
Jun 17 #Python
You might like
JS中encodeURIComponent函数用php解码的代码
2012/03/01 PHP
如何设置mysql允许外网访问
2013/06/04 PHP
php使用simplexml_load_file加载XML文件并显示XML的方法
2015/03/19 PHP
PHP自定义函数实现格式化秒的方法
2016/09/14 PHP
PHP异常类及异常处理操作实例详解
2018/12/19 PHP
在js中单选框和复选框获取值的方式
2009/11/06 Javascript
javaScript call 函数的用法说明
2010/04/09 Javascript
20行代码实现的一个CSS覆盖率测试脚本
2013/07/07 Javascript
关于js数组去重的问题小结
2014/01/24 Javascript
基于promise.js实现nodejs的promises库
2014/07/06 NodeJs
javascript用函数实现对象的方法
2015/05/14 Javascript
Javascript中String的常用方法实例分析
2015/06/13 Javascript
JavaScript中使用sencha gridpanel 编辑单元格、改变单元格颜色
2015/11/26 Javascript
jQuery实现div拖拽效果实例分析
2016/02/20 Javascript
BootstrapTable refresh 方法使用实例简单介绍
2017/02/20 Javascript
Three.js的使用及绘制基础3D图形详解
2017/04/27 Javascript
jQuery extend()详解及简单实例
2017/05/06 jQuery
javascript  删除select中的所有option的实例
2017/09/17 Javascript
基于Vue2的独立构建与运行时构建的差别(详解)
2017/12/06 Javascript
搭建vue开发环境
2018/07/19 Javascript
vue移动端项目缓存问题实践记录
2018/10/29 Javascript
全面解析js中的原型,原型对象,原型链
2021/01/25 Javascript
Python操作sqlite3快速、安全插入数据(防注入)的实例
2014/04/26 Python
Python抓取电影天堂电影信息的代码
2016/04/07 Python
对pandas读取中文unicode的csv和添加行标题的方法详解
2018/12/12 Python
Scrapy框架爬取西刺代理网免费高匿代理的实现代码
2019/02/22 Python
用uWSGI和Nginx部署Flask项目的方法示例
2019/05/05 Python
Python企业编码生成系统总体系统设计概述
2019/07/26 Python
python基于event实现线程间通信控制
2020/01/13 Python
浅谈python 中的 type(), dtype(), astype()的区别
2020/04/09 Python
Python截图并保存的具体实例
2021/01/14 Python
匡威荷兰官方网站:Converse荷兰
2018/10/24 全球购物
澳大利亚最超值的自行车之家:Reid Cycles
2019/03/24 全球购物
期末自我鉴定
2014/01/23 职场文书
中国文明网向国旗敬礼寄语大全
2014/09/27 职场文书
nginx rewrite功能使用场景分析
2022/05/30 Servers