前言
前文中我們把訓(xùn)練好的模型打包成GraphDef文件(PB文件)了莫杈,可是打包出來的文件還是有點(diǎn)大互例;移動設(shè)備的內(nèi)存容量有限,而且我們需要下載模型到移動端去加載姓迅,所以一個大的模型要經(jīng)過壓縮之后才能放在Android或者IOS上跑的敲霍,不然會給RAM造成極大的負(fù)擔(dān),系統(tǒng)會自行kill掉一些進(jìn)程丁存,甚至你的APP自己crash了肩杈。所以模型壓縮是十分必要的。
正文
在Google的優(yōu)化方案中解寝,主要有那么幾種優(yōu)化方式:刪除未使用節(jié)點(diǎn)扩然,刪除training所需的ops,對權(quán)重進(jìn)行四舍五入運(yùn)算聋伦,對BN進(jìn)行預(yù)處理夫偶,內(nèi)存映射,以及終極傷敵1000自損800的方案—轉(zhuǎn)換權(quán)重數(shù)據(jù)類型觉增。本文將逐一介紹兵拢。
optimize_for_inference
調(diào)用optimize_for_inference腳本會刪除輸入和輸出節(jié)點(diǎn)之間所有不需要的節(jié)點(diǎn)。同時該腳本還做了一些其他優(yōu)化以提高運(yùn)行速度逾礁。例如它把顯式批處理標(biāo)準(zhǔn)化運(yùn)算跟卷積權(quán)重進(jìn)行了合并说铃,從而降低了計算量。
使用方法:
bazel build tensorflow/python/tools:optimize_for_inference
bazel-bin/tensorflow/python/tools/optimize_for_inference \
–input=/tf_files/CTNModel.pb \
–output=/tf_files/optimized_graph.pb \
–input_names=inputs/X \
–output_names=output/predict
首先用bazel build出我們的optimize_for_inference腳本嘹履,再調(diào)用這個腳本腻扇,提供幾個參數(shù):輸入的PB文件路徑,輸出的PB文件路徑砾嫉,輸入節(jié)點(diǎn)名以及輸出節(jié)點(diǎn)名幼苛。
經(jīng)過這一次的優(yōu)化,文件變小了一些焕刮,可是還不足以我們放到手機(jī)端去運(yùn)行舶沿,所以我們要進(jìn)一步的壓縮模型,同時還要保證準(zhǔn)確率配并。
我們可以使用之前的在Python端測試PB文件的代碼測試優(yōu)化后的PB文件是否可行括荡。
quantize_graph
在IOS或者Android項目中,人們通常會把PB文件放在assets文件夾中加載荐绝,不管是一起打包進(jìn)APP還是進(jìn)入APP后再進(jìn)行下載一汽、解壓避消、復(fù)制低滩,在經(jīng)過optimize_for_inference優(yōu)化過后的模型依然是非常的大的召夹。IOS在使用.ipa包發(fā)布APP的時候,所有內(nèi)容都會經(jīng)過zip壓縮恕沫;Android從網(wǎng)絡(luò)端下載文件也要經(jīng)過壓縮后解壓的過程监憎,所以有沒有一種行之有效的方法在不過多的降低精確度的情況下壓縮更大的空間呢?Google提供了這么一個腳本婶溯,經(jīng)過這個腳本的PB文件原本的大小不會改變鲸阔,但會有更多的可利用的重復(fù)性,所以壓縮成zip包會縮小大約3~4倍的大小迄委。使用方式很簡單:
bazel build tensorflow/tools/quantization:quantize_graph
bazel-bin/tensorflow/tools/quantization/quantize_graph \
–input=/tf_files/optimized_graph.pb \
–output=/tf_files/rounded_graph.pb \
–output_node_names=output/predict \
–mode=weights_rounded
輸入的參數(shù)依然是:輸入的PB文件路徑褐筛,輸出的PB文件路徑,輸出節(jié)點(diǎn)名叙身,這里還有個特別的參數(shù)mode渔扎,這個參數(shù)是告訴腳本我們選擇哪種壓縮方式,這里我們選擇了對權(quán)重進(jìn)行四舍五入信轿。
graph_transforms
在2017年的Google I/O大會上晃痴,曾提到過的graph_transforms是一整套優(yōu)化工具,基本上所有的優(yōu)化方案都能在這里找到财忽,包括之前的那兩種優(yōu)化方式倘核。
Optimizing for Deployment
這個方式類似前面的optimize_for_inference,只是被整合到了transforms中即彪,腳本進(jìn)行了刪除前向傳播過程中未調(diào)用到的節(jié)點(diǎn)紧唱,通過預(yù)先乘卷積的權(quán)重來優(yōu)化BN中的一些乘法運(yùn)算。
示例代碼:
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=CTNModel.pb \
--out_graph=optimized_graph.pb \
--inputs='inputs/X' \
--outputs='output/predict' \
--transforms='
strip_unused_nodes(type=float, shape="256*64")
remove_nodes(op=Identity, op=CheckNumerics)
fold_constants(ignore_errors=true)
fold_batch_norms
fold_old_batch_norms'
transforms的參數(shù)代表著想要做的操作祖凫,這里主要就是刪除未調(diào)用的節(jié)點(diǎn)琼蚯,以及優(yōu)化BN算法,而且必須先運(yùn)行fold_constants惠况。
Fixing Missing Kernel Errors
由于TensorFlow在Mobile中使用的時候遭庶,默認(rèn)情況下是只能推理預(yù)測,可是在build so文件稠屠、jar文件或者.a文件的時候峦睡,依賴文件是寫入在tensorflow / contrib / makefile / tf_op_files.txt中的,里面還包含了一些training相關(guān)的ops权埠,這些可能會導(dǎo)致我們在加載PB文件的時候報錯—No OpKernel was registered to support Op
榨了,這時候我們可以通過這個腳本來修復(fù)
實(shí)際上,經(jīng)過上面的腳本攘蔽,已經(jīng)刪除了這部分節(jié)點(diǎn)龙屉,不需要重復(fù)使用。
示例代碼:
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=CTNModel.pb \
--out_graph=fixed_kernel_graph.pb \
--inputs='inputs/X' \
--outputs='output/predict' \
--transforms='
strip_unused_nodes(type=float, shape="256*64")
fold_constants(ignore_errors=true)
fold_batch_norms
fold_old_batch_norms'
round_weights
上文中提及的quantize_graph,其實(shí)在graph_transforms也有转捕,在graph_transforms中我們應(yīng)該如何使用呢作岖?
示例代碼:
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=CTNModel.pb \
--out_graph=rounded_grapg.pb \
--inputs='inputs/X' \
--outputs='output/predict' \
--transforms='
strip_unused_nodes(type=float, shape="256*64")
fold_constants(ignore_errors=true)
fold_batch_norms
fold_old_batch_norms
round_weights(num_steps=256)'
Eight-bit
在graph_transforms中有一種更加殘暴的文件大小壓縮方式,就是把權(quán)重的數(shù)據(jù)類型由32位的float 32轉(zhuǎn)成8位的int五芝,在數(shù)據(jù)類型層面為模型減少3~4倍的占用大小痘儡。示例圖如下:
原本80多M的文件經(jīng)過這個轉(zhuǎn)換縮減成20M左右,成效還是挺明顯的枢步,不過缺點(diǎn)就是相比之前的幾種壓縮方式沉删,這種壓縮方式損失精度較大,非必要情況醉途,建議別使用矾瑰。
使用方法:
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=CTNModel.pb \
--out_graph=eight_bit_graph.pb \
--inputs='inputs/X' \
--outputs='output/predict' \
--transforms='
strip_unused_nodes(type=float, shape="256*64")
fold_constants(ignore_errors=true)
fold_batch_norms
fold_old_batch_norms
quantize_weights'
再次提醒,非極端情況請勿使用隘擎!
由上面幾種grapg轉(zhuǎn)換功能來講脯倚,graph_transforms確實(shí)是一個非常強(qiáng)大的優(yōu)化腳本,他能夠一步到位的優(yōu)化我們的PB文件嵌屎,不像之前需要使用多種腳本之后才能得到想要的結(jié)果推正。上文提供了幾種常用的腳本代碼示例,每次使用只需要選其一即可宝惰。
memmapped_format
在前面幾種壓縮之后植榕,如果文件還是較大,那該怎么辦呢尼夺,這時候可以采用內(nèi)存映射的方式尊残,這一種方式是在運(yùn)行時控制內(nèi)存占用的一種有效方式,只是使用起來與原本的PB文件調(diào)用方式有些不同(暫時找不到Android的資料淤堵,只有IOS的寝衫,代碼詳見example)
示例圖如下:
從整個文件讀到內(nèi)存中變成內(nèi)存的映射,這能大量的節(jié)省內(nèi)存帶寬以及占用量拐邪。
使用方法:
bazel build tensorflow/contrib/util:convert_graphdef_memmapped_format
bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format \
–in_graph=/tf_files/rounded_graph.pb \
–out_graph=/tf_files/mmapped_graph.pb
經(jīng)過這個腳本輸出的PB文件不能再Python中直接調(diào)用慰毅,所以我們暫時也無法檢測其可行性,需要在IOS端調(diào)用扎阶。
后記
以上是PB文件常見的壓縮方法汹胃,在TensorFlow移植Mobile的過程中還有其他的優(yōu)化方案,比如優(yōu)化TensorFlow依賴庫的大小东臀,對ops進(jìn)行高級定制着饥,以刪除不需要的ops,此方法需要針對不同的model進(jìn)行不同的定制惰赋,較為高端宰掉。還有就是對模型進(jìn)行優(yōu)化比如嘗試減少層數(shù)、減少節(jié)點(diǎn)數(shù)、對卷積運(yùn)行做優(yōu)化(點(diǎn)卷積降維(inception)轨奄、卷積拆分(MobileNets))這些都是較為高級的Mobile端壓縮方式仇穗,本文暫時不做介紹了。
歡迎各位指錯討論戚绕。