本文将介绍对Keras模型训练过程进行加速的方法。重点介绍Google 的Colab平台的免费GPU资源使用攻略。
一,训练过程的耗时分析
深度学习模型的训练过程常常会非常耗时,一个模型训练几天是非常常见的事情,甚至有时候就如同太上老君用八卦炉冶炼仙丹一样,要七七四十九天才能够让模型出炉,因此有时候机器学习工程师也被比喻成"炼丹师"。
训练过程的耗时主要来自于两个部分,一部分来自 数据准备 ,另一部分来自 参数迭代 。
由于喂养深度学习模型的训练集数据常常达到几十G以上,无法一次载入内存,因此需要在训练过程中从磁盘中不断读入并做适当转换,IO过程和转换过程是比较费时的,为了减少这部分时间的占用,我们一般通过多进程或多线程的方式构建并行数据输入管道来准备数据。
当数据准备过程还是模型训练时间的主要瓶颈时,我们可以使用更多进程或线程来准备数据。就好像模型怪兽食欲太好吃数据吃的太快了,就让多个厨师同时开火准备数据点心端上饭桌。
参数迭代过程主要是大量的简单矩阵运算,参数矩阵越大模型越深迭代次数越多耗时越长。矩阵运算本质上是一种简单重复的并行计算,当前的普通CPU通常只有4-8个core,因此做大型矩阵运算效率并不高。而普通的GPU通常都有几百上千个core,做矩阵运算有天然的优势。
当参数迭代过程成为训练时间的主要瓶颈时,我们通常的方法是应用GPU或者Google的TPU来进行加速,可以简单地把TPU看成打包在一起的多个GPU。就好像模型怪兽嘴巴里的牙不太够,咀嚼数据点心的速度有点慢,我们给就给它换一个有成百上千个牙齿的GPU大嘴巴,让它成为一只饕餮猛兽。
在实践中训练模型时,有时候会发现换成了GPU后模型的训练时间并没有怎么变化,那么这种情况下通常是因为数据准备过程是速度的主要瓶颈,应当先增加准备数据的进程数。
二,GPU计算资源的获取方法
获取GPU计算资源的方法大概可以分成以下3种。
1,土豪之选
直接购买GPU硬件。
通常一块用于深度学习的GPU价格在几千到几万元人民币不等。企业或者有较强经济实力的的个人一般采用该方案。
土豪之选方案的优点是一次付费长期使用,稳定可靠,想跑多久跑多久。该方案的缺点是比较费钱,并且需要费些时间去安装cuda,cuDNN,以及tensorflow-gpu等以支持keras使用GPU进行模型训练。
2,中产之选
购买云端GPU计算时长。
各家主要的云厂商都提供了GPU计算资源的按需租用服务。但比较推荐的是Floydhub和国内的极客云这两个深度学习云平台。这些深度学习云平台提供了已经配置好了各种机器学习库和GPU支持的云端jupyter notebook,只要上传数据就可以直接跑模型,非常贴心。极客云的GPU时长价格目前在5元左右。
中产之选方案的优点是非常省心,扩展灵活,只把时长用在刀刃上的话价格不贵。该方案的缺点是 heavy user 容易因此破产。
3,难民之选
使用云端免费GPU资源。
目前发现的比较可靠的提供免费GPU计算资源的有两个平台,一个是Google Colaboratory,另外一个是Kaggle kernel。由于Kaggle在2017年被Google收购了,所以可以认为都是Google在后面买单。所以说Google是真土豪,这里诚挚地代表难民们感谢一下土豪。
由于国内防火墙的原因,Colab要搭建梯子后才能够访问使用。而Kaggle kernel除了在注册时获取验证码和上传数据集时需要短暂连接国外网站外,此后无需梯子也可以正常使用。从使用体验上来讲,两个平台都是第一流的,但Colab上传数据似乎更加高效方便一些。故我们这里介绍Colab的使用攻略。
难民之选方案的优点是非常省钱,Colab还可以使用TPU。缺点是只有一个GPU,计算资源有限且不方便扩展,并且由于是外网,上传数据速度往往会比较慢。而且,任务计算时长超过12个小时后会断开连接,如果是训练特别大的模型,需要设置断点续训。不过,对于大部分同学以学习或参加一些小比赛为主要目的来说,12个小时已经是够够的了。
让我们出发吧,去打劫一下Google土豪!
三,Colab免费GPU使用攻略
1,登陆Google Drive
Google Drive的网址是:https://drive.google.com/drive/
如果没有google账号,需要注册google的gmail邮箱账号。
这一步本来应该非常简单,对于大部分同学来说,主要的困难可能来自于无法访问google。网络上有大量有关科学上网的教程,大家可以自己去找一下。如果实在有困难的同学,也可以在公众号后台回复:Colab,找我讨论一下这个问题。好吧,这个问题我们就点到为止,毕竟我们都不想去喝茶。
2,新建Colab notebook
依次点击 新建/更多/Colabarory即可,下面这幅动画展示了这个过程,非常简单。
3,设置GPU加速选项
在 修改/笔记本设置/硬件加速器 下拉菜单选择GPU即可。
通过运行 nvidia-smi命令,我们可以查看GPU的一些基本信息。
可以看到,我们只有一块GPU,其型号是Tesla T4,显存大小为15G左右。
4,上传训练数据
我们使用《Keras图像数据预处理范例——Cifar2图片分类》文章中提到的Cifar2数据集的分类项目来演示GPU对Keras模型训练过程的的加速效果。
公众号后台回复关键字 cifar2 ,可以获取该数据集的下载链接。
然后通过以下linux命令解压数据到cifar2_datasets路径即可
5,运行模型代码
从原理上说,无需更改任何代码,keras模型可以无缝从CPU上迁移到GPU机器上运行。当存在可用的GPU时,如果不特意指定device,keras的后端tensorflow(GPU版本)会自动优先选择使用GPU来创建张量和执行张量计算。
但如果是在公司或者学校实验室的服务器环境,存在多个GPU和多个使用者时,为了不让单个同学的任务占用全部GPU资源导致其他同学无法使用(tensorflow默认获取全部GPU的全部内存资源权限,但实际上只使用一个GPU的部分资源),我们通常会在开头增加以下几行代码以控制每个任务使用的GPU编号和显存比例,以便其他同学也能够同时训练模型。
# ======================================================================
# 〇,设置gpu使用量控制
import os
import tensorflow as tf
from keras.backend.tensorflow_backend import set_session
os.environ["CUDA\_VISIBLE\_DEVICES"] = "0" #有多个GPU时可以指定只使用第几号GPU
config = tf.ConfigProto()
config.allow_soft_placement=True #允许动态放置张量和操作符
config.gpu_options.per_process_gpu_memory_fraction = 0.4 #最多使用40%GPU内存
config.gpu_options.allow_growth=True #初始化时不全部占满GPU显存, 按需分配
sess = tf.Session(config = config)
set_session(sess)
以下为完整模型训练代码,除了开头部分是上述控制GPU使用量的代码外,其余代码与《Keras图像数据预处理范例——Cifar2图片分类》中的代码几乎一致。
1# coding=utf-8
2from __future__ import print_function
3from __future__ import division
4
5__author__ = 'Python\_Ai\_Road'
6
7# ======================================================================
8# 〇,设置gpu使用量控制
9
10import os
11import tensorflow as tf
12from keras.backend.tensorflow_backend import set_session
13
14os.environ["CUDA\_VISIBLE\_DEVICES"] = "0" #有多个GPU时可以指定只使用第几号GPU
15config = tf.ConfigProto()
16config.allow_soft_placement=True #允许动态放置张量和操作符
17config.gpu_options.per_process_gpu_memory_fraction = 0.4 #最多使用40%GPU内存
18config.gpu_options.allow_growth=True #初始化时不全部占满GPU显存, 按需分配
19sess = tf.Session(config = config)
20set_session(sess)
21
22
23# ======================================================================
24# 一,准备数据
25
26from keras.preprocessing.image import ImageDataGenerator
27
28train_dir = 'cifar2\_datasets/train'
29test_dir = 'cifar2\_datasets/test'
30
31# 对训练集数据设置数据增强
32train_datagen = ImageDataGenerator(
33 rescale = 1./255,
34 rotation_range=40,
35 width_shift_range=0.2,
36 height_shift_range=0.2,
37 shear_range=0.2,
38 zoom_range=0.2,
39 horizontal_flip=True,
40 fill_mode='nearest')
41
42# 对测试集数据无需使用数据增强
43test_datagen = ImageDataGenerator(rescale=1./255)
44
45train_generator = train_datagen.flow_from_directory(
46 train_dir,
47 target_size=(32, 32),
48 batch_size=32,
49 shuffle = True,
50 class_mode='binary')
51
52test_generator = test_datagen.flow_from_directory(
53 test_dir,
54 target_size=(32, 32),
55 batch_size=32,
56 shuffle = False,
57 class_mode='binary')
58
59# ======================================================================
60# 二,构建模型
61
62from keras import models,layers,optimizers
63from keras import backend as K
64
65K.clear_session()
66model = models.Sequential()
67model.add(layers.Flatten(input_shape = (32,32,3)))
68model.add(layers.Dense(1024, activation='relu'))
69model.add(layers.Dense(1024, activation='relu'))
70model.add(layers.Dense(1024, activation='relu'))
71model.add(layers.Dense(1, activation='sigmoid'))
72
73model.compile(loss='binary\_crossentropy',
74 optimizer=optimizers.RMSprop(lr=1e-4),
75 metrics=['acc'])
76
77model.summary()
78
79# ======================================================================
80# 三,训练模型
81
82# 计算每轮次需要的步数
83import numpy as np
84train_steps_per_epoch = np.ceil(10000/32)
85test_steps_per_epoch = np.ceil(2000/32)
86
87import time
88tic = time.time()
89# 使用内存友好的fit\_generator方法进行训练
90history = model.fit_generator(
91 train_generator,
92 steps_per_epoch = train_steps_per_epoch,
93 epochs = 5,
94 validation_data= test_generator,
95 validation_steps=test_steps_per_epoch,
96 workers=6,
97 use_multiprocessing=True #linux上可使用多进程读取数据
98 )
99toc = time.time()
100
101print('\nused time:',toc - tic,'\n')
102
103
104# ======================================================================
105# 四,评估模型
106import pandas as pd
107import matplotlib.pyplot as plt
108#%matplotlib inline
109#%config InlineBackend.figure\_format = 'png'
110
111dfhistory = pd.DataFrame(history.history)
112dfhistory.index = range(1,len(dfhistory) + 1)
113dfhistory.index.name = 'epoch'
114dfhistory.to_csv('hitory\_metrics',sep = '\t')
115
116
117acc = history.history['acc']
118val_acc = history.history['val\_acc']
119epochs = range(1, len(acc) + 1)
120plt.plot(epochs, acc, 'bo', label='Training accuracy')
121plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
122plt.title('Training and validation accuracy')
123plt.xlabel('Epochs')
124plt.ylabel('Accuracy')
125plt.legend()
126plt.show()
127
128# ======================================================================
129# 五,使用模型
130
131from sklearn.metrics import roc_auc_score
132
133test_datagen = ImageDataGenerator(rescale=1./255)
134
135# 注意,使用模型进行预测时要设置生成器shuffle = False
136test_generator = test_datagen.flow_from_directory(
137 test_dir,
138 target_size=(32, 32),
139 batch_size=32,
140 class_mode='binary',
141 shuffle = False)
142
143# 计算auc
144y_pred = model.predict_generator(test_generator,steps = len(test_generator))
145y_pred = np.reshape(y_pred,(-1,))
146y_true = np.concatenate([test_generator[i][1]
147 for i in range(len(test_generator))])
148auc = roc_auc_score(y_true,y_pred)
149
150print('test auc:', auc)
151
152# ======================================================================
153# 六,保存模型
154
155model.save('cifar2\_model.h5')
156
157######
158#####
159####
160###
161##
162#
由于我们在model.fit_generator里使用了6个进程来读取数据,基本上数据准备过程不会成为瓶颈,训练时间主要取决于参数迭代时间。经过试验,在我们这个例子中,不使用硬件加速器时,模型训练完成用时 187.6s ,使用GPU硬件加速器时模型训练完成用时 53.2s ,约有 3倍 多的加速效果。当模型参数更多,张量计算任务更加繁重时,GPU的加速效果更加明显,有时候能够达到5倍到10倍的提升。
老铁,不走一个试试看吗?
推荐阅读: