最近使用keras訓(xùn)練了一個(gè)圖像分割的模型(.h5),但最終需要在C++中調(diào)用該模型,,由于keras沒有C++接口,,所以需要將.h5模型轉(zhuǎn)換為.pb模型后通過tensorflow C++接口進(jìn)行調(diào)用,。
由于本人之前接觸深度學(xué)習(xí)較少,很多東西不是很懂,,所以在轉(zhuǎn)換過程中遇到了很多問題,在此記錄,,共同學(xué)習(xí),。
1,、轉(zhuǎn)換之前需要注意的點(diǎn)
- 本人在轉(zhuǎn)換過程中發(fā)現(xiàn)tensorflow1.x和2.x存在區(qū)別,所以在轉(zhuǎn)換之前最好確定訓(xùn)練模型時(shí)使用的tensorflow版本,,轉(zhuǎn)換過程使用的環(huán)境盡量和訓(xùn)練模型使用的環(huán)境保持一致,不然容易產(chǎn)生很多錯(cuò)誤,。
- 導(dǎo)出模型時(shí)使用的API,,是使用tensorflow.keras還是keras,,和轉(zhuǎn)換時(shí)使用的API保持一致,,因?yàn)閮烧呖赡懿患嫒輳亩鴮?dǎo)致錯(cuò)誤。
- tensorflow版本:明確當(dāng)前使用的是1.x還是2.x版本
- keras版本:與tensorflow版本相匹配,,查看tensorflow版本與keras版本對(duì)應(yīng)關(guān)系
- tensorflow各版本whl下載地址
2,、tensorflow1.x轉(zhuǎn)換方法
使用環(huán)境說明:python3.6.12+tensorflow1.15.0+keras2.2.4
1、常用的方法基本均來自github:keras_to_tensorflow,,直接clone下來使用即可,。 使用方法: (1)命令行方式:input_model輸入.h5路徑,output輸入保存.pb路徑,。
python keras_to_tensorflow.py
--input_model="./model.h5"
--output_model="./model.pb"
(2)將參數(shù)寫入代碼中,方便調(diào)試,。 2,、簡化后的轉(zhuǎn)換代碼
from tensorflow.python.keras.models import load_model #from keras.models import load_model
import tensorflow as tf
from tensorflow.python.keras import backend as K #from keras import backend as K
from tensorflow.python.framework import graph_io
def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):
from tensorflow.python.framework.graph_util import convert_variables_to_constants
graph = session.graph
with graph.as_default():
freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
output_names = output_names or []
output_names += [v.op.name for v in tf.global_variables()]
input_graph_def = graph.as_graph_def()
if clear_devices:
for node in input_graph_def.node:
node.device = ""
frozen_graph = convert_variables_to_constants(session, input_graph_def,
output_names, freeze_var_names)
return frozen_graph
"""----------------------------------配置路徑-----------------------------------"""
h5_model_path='./unet.h5' #填寫.h5路徑
output_path='.'
pb_model_name='unet.pb' #填寫保存.pb路徑
"""----------------------------------導(dǎo)入keras模型------------------------------"""
K.set_learning_phase(0)
net_model = load_model(h5_model_path)
print('input is :', net_model.input.name)
print ('output is:', net_model.output.name)
"""----------------------------------保存為.pb格式------------------------------"""
sess = K.get_session()
frozen_graph = freeze_session(K.get_session(), output_names=[net_model.output.op.name])
graph_io.write_graph(frozen_graph, output_path, pb_model_name, as_text=False)
3、常見錯(cuò)誤
(1) TypeError:Keras symbolic inputs/outputs do not implement op 解決方法:在使用tensorflow1.x時(shí)不會(huì)出現(xiàn)該錯(cuò)誤,,若使用tensorflow2.x會(huì)出現(xiàn),。此時(shí)可切換至tensorflow1.x;或?qū)?code>.op直接刪除,,修改為net_model.output.name 也行,,但是大概率還會(huì)在其它地方報(bào)錯(cuò)… 所以最好還是使用tensorflow1.x,若上述方法解決不了,,且需要tensorflow2.x,,請(qǐng)看tensorflow2.x轉(zhuǎn)換方法,。
(2) TypeError: __init__() got an unexpected keyword argument 'ragged' 解決方法:可能是因?yàn)閷?dǎo)出模型和轉(zhuǎn)換模型使用的API不一致導(dǎo)致,查看導(dǎo)出模型使用的API屬于tensorflow.keras還是keras,。若轉(zhuǎn)換模型代碼中使用:
from keras.models import load_model
from keras import backend as K
修改為:
from tensorflow.python.keras.models import load_model
from tensorflow.python.keras import backend as K
(3) TypeError: Unknown layer: Functional 解決方法:我報(bào)錯(cuò)的原因是因?yàn)槟P褪褂胻ensorflow2.4.1訓(xùn)練,,轉(zhuǎn)換時(shí)使用tensorflow1.15.0,導(dǎo)致該錯(cuò)誤,,主要問題還是因?yàn)榄h(huán)境不一致,。 或者是模型中存在自定義層,需要在load_model中顯式指出,,解決該問題的方法是在load_model函數(shù)中添加custom_objects參數(shù),,該參數(shù)接受一個(gè)字典,鍵值為自定義的層(參考博客),。
(4) TypeError: ('Unrecognized keyword arguments:', dict_keys(['ragged']))
解決方法:可能是因?yàn)閠ensorflow版本太低,,建議使用更高版本。
(5) TypeError: module 'tensorflow' has no attribute 'global_variables'
解決方法:當(dāng)前tensorflow版本為2.x,,可嘗試如下修改方式,。
tf.compat.v1.global_variables() #類似錯(cuò)誤均可將tf修改為tf.compat.v1
(6) TypeError: *** is not in graph 解決方法:錯(cuò)誤說明:輸出節(jié)點(diǎn)不在圖中(如圖中conv2d_18/Relu:0)。
- convert_variables_to_constants函數(shù)用來指定保存的 節(jié)點(diǎn)名稱 而不是 張量的名稱 ,, “conv2d_18/Relu:0” 是張量的名稱而 “conv2d_18/Relu” 表示的是節(jié)點(diǎn)的名稱,。嘗試將參數(shù)output_names直接指定為“conv2d_18/Relu”,如:
output_names= ['conv2d_18/Relu']
frozen_graph = convert_variables_to_constants(session, input_graph_def,
output_names, freeze_var_names)
- 確認(rèn)輸出節(jié)點(diǎn)名稱是否正確,。
- 若上述方法無法解決,,大概率還是tensorflow版本不一致導(dǎo)致。
3,、tensorflow2.x轉(zhuǎn)換方法
使用環(huán)境說明:python3.8.5+tensorflow2.4.1+keras2.4.0
1、轉(zhuǎn)換代碼
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
def h5_to_pb(h5_save_path):
model = tf.keras.models.load_model(h5_save_path, compile=False)
model.summary()
full_model = tf.function(lambda Input: model(Input))
full_model = full_model.get_concrete_function(tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype))
# Get frozen ConcreteFunction
frozen_func = convert_variables_to_constants_v2(full_model)
frozen_func.graph.as_graph_def()
layers = [op.name for op in frozen_func.graph.get_operations()]
print("-" * 50)
print("Frozen model layers: ")
for layer in layers:
print(layer)
print("-" * 50)
print("Frozen model inputs: ")
print(frozen_func.inputs)
print("Frozen model outputs: ")
print(frozen_func.outputs)
# Save frozen graph from frozen ConcreteFunction to hard drive
tf.io.write_graph(graph_or_graph_def=frozen_func.graph,
logdir="./pb",
name="model.pb",
as_text=False) #可設(shè)置.pb存儲(chǔ)路徑
h5_to_pb('./unet.h5') #此處填入.h5路徑
4,、python環(huán)境下測(cè)試pb
需要根據(jù)實(shí)際情況進(jìn)行修改,,需要修改的地方在代碼中已經(jīng)標(biāo)出。
import tensorflow as tf
import numpy as np
import cv2
def recognize(jpg_path, pb_file_path):
with tf.Graph().as_default():
output_graph_def = tf.compat.v1.GraphDef()
# 打開.pb模型
with open(pb_file_path, "rb") as f:
output_graph_def.ParseFromString(f.read())
tensors = tf.import_graph_def(output_graph_def, name="")
print("tensors:",tensors)
# 在一個(gè)session中去run一個(gè)前向
with tf.compat.v1.Session() as sess:
init = tf.compat.v1.global_variables_initializer()
sess.run(init)
op = sess.graph.get_operations()
# 打印圖中有的操作
for i,m in enumerate(op):
print('op{}:'.format(i),m.values())
input_x = sess.graph.get_tensor_by_name("Input:0") # 具體名稱看上一段代碼的input.name
print("input_X:",input_x)
out_softmax = sess.graph.get_tensor_by_name("Identity:0") # 具體名稱看上一段代碼的output.name
print("Output:",out_softmax)
# 讀入圖片
img = cv2.imread(jpg_path, 1) #注意讀入灰度圖還是彩圖
img=cv2.resize(img,(512,512)) #需要和訓(xùn)練模型時(shí)圖像resize大小保持一致
#img=img.astype(np.float32)
#img=1-img/255;
# img=np.reshape(img,(1,28,28,1))
print("img data type:",img.dtype)
# 顯示圖片內(nèi)容
# for row in range(512):
# for col in range(512):
# if col!=511:
# print(img[row][col],' ',end='')
# else:
# print(img[row][col])
img_out_softmax = sess.run(out_softmax,
feed_dict={input_x: np.reshape(img,(1,512,512,3))}) #圖像大小保持一致
#轉(zhuǎn)換為可保存圖像
show_image = img_out_softmax.reshape(512, 512, 3)
show_image = show_image.astype(np.uint8)
cv2.imwrite('result.jpg',show_image)
print("img_out_softmax:", img_out_softmax)
for i,prob in enumerate(img_out_softmax[0]):
print('class {} prob:{}'.format(i,prob))
prediction_labels = np.argmax(img_out_softmax, axis=1)
print("Final class if:",prediction_labels)
print("prob of label:",img_out_softmax[0,prediction_labels])
pb_path = './model.pb' #pb路徑
img = '1107.bmp' #圖像路徑
recognize(img, pb_path)
5,、總結(jié)
綜上所述:大多數(shù)問題均是由tensorflow版本不一致導(dǎo)致,,在解決bug之前最好將版本對(duì)應(yīng),這樣可能會(huì)很快解決你的問題,。
|