本文總結(jié)了Tensorflow內(nèi)存溢出的各種原因戳寸,以及在此排查過程中使用的方法呈驶。
本文在實現(xiàn)Tensorflow模型的保存(save)和重新調(diào)用(restore)過程中,程序總是發(fā)生內(nèi)存溢出而中止的問題疫鹊,所以對其進(jìn)行故障排查袖瞻。
排查工具及步驟
1. Tensorboard
Tensorboard是Tensorflow提供的向用戶展示模型結(jié)構(gòu)以及運行結(jié)果等的可視化工具司致。當(dāng)Tensorflow相關(guān)程序發(fā)生內(nèi)存溢出,并且確認(rèn)程序所使用的數(shù)據(jù)量并不大時聋迎,首先想到使用Tensorboard對模型結(jié)構(gòu)進(jìn)行可視化脂矫,在此之前,需要對模型中的各個變量(variable, placeholder)砌庄、節(jié)點(nodes)羹唠、以及節(jié)點域進(jìn)行命名,從而方便可視化后對模型各個部分的辨別和故障排查娄昆。例如:
import tensorflow as tf
with tf.name_scope('Input'):
batchX_placeholder = tf.placeholder(tf.float32, [input_size, look_back], name='Input_Placeholder')
batchY_placeholder = tf.placeholder(tf.float32, [output_size, look_back], name='Output_Placeholder')
這段代碼對應(yīng)在Tensorboard中如下圖所示:
類似地佩微,搭建出整個模型的結(jié)構(gòu)圖之后,通過如下代碼將圖保存在文件當(dāng)中萌焰,之后就可以通過tensorboard --logdir=filedir --host=127.0.0.1運行Tensorboard哺眯,并使用瀏覽器打開。
train_writer = tf.summary.FileWriter('graph', sess.graph) # save the graph
完成以上步驟后扒俯,進(jìn)行核對奶卓,發(fā)現(xiàn)graph的結(jié)構(gòu)會隨著程序運行逐漸變大,原因在于在生成新的結(jié)構(gòu)時撼玄,沒有將之前的結(jié)構(gòu)刪去夺姑,于是,通過如下代碼掌猛,每次生成新的圖時盏浙,將之前的結(jié)構(gòu)重置(刪去):
tf.reset_default_graph() # reset the previous graph and nodes
結(jié)果:Graph不再隨著新的模型聲明而積累舊的結(jié)構(gòu),內(nèi)存溢出問題得到緩解荔茬,但并沒有完全解決废膘。
2. 內(nèi)存監(jiān)視
在查找了程序書寫結(jié)構(gòu)上的問題之后,沒有什么頭緒慕蔚,于是嘗試監(jiān)視內(nèi)存查看程序運行問題丐黄。
import psutil
def memory_monitor():
# https://github.com/giampaolo/psutil
# 1. virtual_memory():
# total: total physical memory.
# available: the memory that can be given instantly to processes without the system going into swap.
# This is calculated by summing different memory values depending on the platform and it
# is supposed to be used to monitor actual memory usage in a cross platform fashion.
# 2. swap_memory():
# total: total swap memory in bytes
# used: used swap memory in bytes
print psutil.virtual_memory()
print psutil.swap_memory()
將該函數(shù)插入到想要查看當(dāng)前內(nèi)存使用情況的位置,但是沒有任何發(fā)現(xiàn)孔飒。
3. 給Tensorflow結(jié)構(gòu)分配給不同的GPU
因為之前Tensorflow的結(jié)構(gòu)是使用CPU進(jìn)行運行灌闺,猜想如果將Tensorflow運算分配給不同的GPU,是否可以解決內(nèi)存溢出的問題坏瞄。
分配GPU和CPU的原則是菩鲜,并行結(jié)構(gòu)和運算分配給不同的GPU,主線運算分配給CPU惦积。
如圖所示,可以通過打開Tensorboard中的Device開關(guān)猛频,可以在Graph中不同結(jié)構(gòu)標(biāo)識不同的device狮崩。
同樣的蛛勉,內(nèi)存溢出的問題并沒有得到解決。
4. 查看不同變量類型的增長情況以及調(diào)用結(jié)構(gòu)
為了進(jìn)一步查找導(dǎo)致內(nèi)存溢出的增長源睦柴,采用python的gc模塊對程序運行過程中的不同變量類型的增長情況以及調(diào)用結(jié)構(gòu)進(jìn)行分析诽凌。代碼如下:
import objgraph
objgraph.show_growth() # show the growth of the objects
objgraph.show_refs(variableName, filename='graph.png') # show the reference structure of the variable
通過將show_growth()放置在程序單次循環(huán)的末尾,以及查看每次循環(huán)所帶來的變量類型的增長情況進(jìn)行分析坦敌,發(fā)現(xiàn)在Tensorflow模型的訓(xùn)練以及預(yù)測過程中侣诵,Tensor類型的變量數(shù)在不斷增加,這就是導(dǎo)致內(nèi)存溢出的關(guān)鍵狱窘。
其間杜顺,使用了gc.collect()對垃圾進(jìn)行手動回收,但是沒有解決問題蘸炸。
雖然程序已經(jīng)包含了之前的tf.reset_default_graph躬络,但是變量數(shù)卻在增加,這點非常奇怪搭儒。為了進(jìn)一步排除問題的可能性穷当,重新聲明一個新的測試類,只包含了模型的聲明淹禾,測試類的結(jié)構(gòu)和調(diào)用方式仿照存在內(nèi)存溢出問題的類馁菜,如下所示:
class testClass(object):
def __init__(self):
pass
def test1(self, CF):
print '------------------------'
objgraph.show_growth()
tf.reset_default_graph()
'''Establish the model'''
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
sess.close()
objgraph.show_growth()
def test2(self, CF):
print '------------------------'
objgraph.show_growth()
tf.reset_default_graph()
'''Establish the model'''
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
sess.close()
objgraph.show_growth()
經(jīng)過測試發(fā)現(xiàn),sess雖然是局部變量铃岔,但是如果不對sess進(jìn)行close操作汪疮,那么之前所有的graph, nodes, variables等等Tensor將會全部被保留下來,(盡管已經(jīng)完成了reset_default_graph)德撬,找到了根源之后铲咨,添加close操作,再通過show_growth可以觀察到蜓洪,在重復(fù)調(diào)用test1和test2時纤勒,內(nèi)存不會產(chǎn)生之前無用的消耗和溢出。
至此隆檀,完成了對tensorflow內(nèi)存溢出問題的排查和調(diào)試摇天。
總結(jié):
出現(xiàn)內(nèi)存溢出問題,可能的原因有很多種恐仑,對于機器學(xué)習(xí)程序泉坐,從數(shù)據(jù)和模型兩方面進(jìn)行入手,首先確認(rèn)數(shù)據(jù)的處理裳仆、調(diào)用等是否存在內(nèi)存溢出的問題腕让,這部分可以通過objgraph模塊內(nèi)的相關(guān)函數(shù)對其進(jìn)行調(diào)試和排查;其次需要對Tensorflow的模型進(jìn)行核查,是否存在舊的節(jié)點和圖未刪去纯丸、Session使用后未關(guān)閉等問題偏形。