TensorFlow 模型导出

TensorFlow 模型导出

https://tf.wiki/zh/deployment/export.html

使用 SavedModel 完整导出模型里的 tf.load_saved_model 方法已经失效. 请及时更新.

你好,这边在 TensorFlow 2.1 下运行了一下示例代码,好像并没有问题。文章里并没有出现tf.load_saved_model这种写法,请您具体说明一下文章的哪一句话或者段落失效,谢谢!

你好,老师,我把模型保存,然后测试时将模型导入后,调用 model.call 时一直报错,我检查了一下代码,我的模型类继承的是 tf.keras.Model,但是我在 call 方面前面加了 tf.function 装饰器啊,不知道为什么还报这个错误
ValueError: Could not find matching function to call loaded from the SavedModel. Got:
Positional arguments (1 total):
* Tensor (“input:0”, shape=(60, 28, 28, 1), dtype=float64)
Keyword arguments: {}

Expected these arguments to match one of the following 1 option (s):

Option 1:
Positional arguments (1 total):
* TensorSpec (shape=(20, 28, 28, 1), dtype=tf.float32, name=‘input’)
Keyword arguments: {}

这是我的完整代码

读取命令行参数

arg=argparse.ArgumentParser ()
arg.add_argument ("–num_epoch",default=5)# 可选命令行参数一般都要给默认值
arg.add_argument ("–batch_size",default=20)
arg.add_argument ("–learning_rate",default=0.001)
arg.add_argument ("–mode",default=“train”)
res=arg.parse_args ()

创建的模型类

class Model (tf.keras.Model):
# 创建模型中的所有层
def init(self):
super ().init()# 要调用 super () 来进行父类的初始化
self.flatten=tf.keras.layers.Flatten ()
self.dense1=tf.keras.layers.Dense (100,activation=tf.nn.relu)
self.dense2=tf.keras.layers.Dense (10,activation=None)
# 写清楚模型前向运算过程
@tf.function
def call (self,input):
x=self.flatten (input)
x=self.dense1 (x)
x=self.dense2 (x)
output=tf.nn.softmax (x)#one-hot 分类任务中一般在最后面都要加 softmax 层
return output

创建的数据类

class Data ():
# 该构造函数读取数据集,并对数据集进行归一化、扩维
def init(self):
(self.train_x,self.train_y),(self.test_x,self.test_y)=tf.keras.datasets.mnist.load_data ()
self.train_x=np.expand_dims (self.train_x.astype (np.float)/255.0,axis=-1)
self.test_x=np.expand_dims (self.test_x.astype (np.float)/255.0,axis=-1)
self.train_y=self.train_y.astype (np.int)
self.test_y=self.test_y.astype (np.int)
self.train_num=self.train_x.shape [0]
self.test_num=self.test_x.shape [0]
# 从训练集中每次采样 batch_size 的样本并返回
def get_data (self,batch_size):
vals=np.random.randint (0,self.train_num,batch_size)
return self.train_x [vals,:],self.train_y [vals]

训练函数

def train ():
# 读取命令行的超参数并且进行模型类和数据类的实例化
num_epochs=int (res.num_epoch)# 命令行传进来的默认为 str 类型,这里注意要把这三个值转化成对应的数值类型
batch_size=int (res.batch_size)
lr=float (res.learning_rate)
model=Model ()

# 创建模型保存器,这里加上 CheckpointManager () 的原因是可以设置一些自己的模型保存方式
# checkpoint=tf.train.Checkpoint (SModel=model)
# manager=tf.train.CheckpointManager (checkpoint,'./save',max_to_keep=3)
data=Data ()
optimizer=tf.keras.optimizers.Adam (learning_rate=lr)
num_size=data.train_num//batch_size*num_epochs
log='log'
# 创建写算子,方便后面将一些参数值写入日志文件
writer=tf.summary.create_file_writer (log)
# 开启日志追踪器,可以将计算图和一些操作的运算时间保存下来
tf.summary.trace_on (graph=True,profiler=True)
for i in range (num_size):
    x,y=data.get_data (batch_size)
    # 梯度计算器里面进行的是模型前向运算和损失函数计算的过程
    with tf.GradientTape () as tape:
        y_pre=model (x)
        loss=tf.keras.losses.sparse_categorical_crossentropy (y_true=y,y_pred=y_pre)
        loss=tf.reduce_sum (loss)
        # 将损失值写入日志文件
        with writer.as_default ():
            tf.summary.scalar ("loss",loss,step=i)
        if i%100==0:
            print ("训练批次为:{}  损失值为{}".format (i,loss))
    grad=tape.gradient (loss,model.variables)
    optimizer.apply_gradients (grads_and_vars=zip (grad,model.variables))
    # if i%100==0:
    #     path=manager.save (checkpoint_number=i)# 给保存的 ckpt 打上编号,并且返回保存路径
    #     print ("模型保存到了:{}".format (path))
# 保存 track 信息到日志文件
with writer.as_default ():
    tf.summary.trace_export ("trace",step=0,profiler_outdir=log)
tf.saved_model.save (model,'Trained_Models')
print ("模型保存完毕!")

训练函数

def test ():
# 往一个新的模型里面加载训练好的参数,这里是查找保存的最新的 ckpt 文件
# model=Model ()
# checkpoint=tf.train.Checkpoint (SModel=model)#SModel 名字要和保存时的名字相同
# checkpoint.restore (tf.train.latest_checkpoint (’./save’))
model=tf.saved_model.load (’./Trained_Models’)
print (“模型已经加载”)
data=Data ()
batch_size=int (res.batch_size)
num_size=data.test_num//batch_size
# 创建精度评估器
metri=tf.keras.metrics.SparseCategoricalAccuracy ()
for i in range (num_size):
x,y=data.test_x [i*batch_size:(i+1)*batch_size,:],data.test_y [i*batch_size:(i+1)*batch_size]
y_pre=model.call (x)
# 更新精度评估器
metri.update_state (y_true=y,y_pred=y_pre)
# 输出最终精度
print (“最终精度为:{}”.format (metri.result ()))
if name==‘main’:
if res.mode==‘train’:
train ()
if res.mode==‘test’:
test ()

这个错误提示看起来似乎是你在训练和测试时的 Batch Size 不一样。可以参考 https://tf.wiki/zh_hans/deployment/serving.html#keras@tf.function 设置 TensorSpec,使用 None 代表 Batch Size 大小可变。

谢谢了,问题解决了,是导入模型后预测时,数据类型的原因,我把 model.call 传入的参数改为了 tf.float32,就 ok 了

tf.saved_model.save (model, “保存的目标文件夹名称”) 里面好像没有地方指定保存的.pb 格式的模型的名称,save 和 load 只能使用文件夹来区分模型吗,模型的名字都必须是 saved_model.pb 吗

参考 Using the SavedModel format  |  TensorFlow Core ,默认情况似乎确实如此。如果有人发现有什么修改的方式也可以贴出来。

执行这段代码,报 ipykernel_launcher.py: error: unrecognized arguments:错误,将 parser.parse_args () 替换成 parser.parse_known_args ()[0],解决问题。代码如下,
%tb import tensorflow as tf
import numpy as np
import argparse
from zh.model.mnist.mlp import MLP
from zh.model.utils import MNISTLoader

parser = argparse.ArgumentParser (description=‘Process some integers.’)
parser.add_argument (’–mode’, default=‘train’, help=‘train or test’)
parser.add_argument (’–num_epochs’, default=1)
parser.add_argument (’–batch_size’, default=50)
parser.add_argument (’–learning_rate’, default=0.0001)
args = parser.parse_known_args ()[0]
data_loader = MNISTLoader ()

為什麼是用model.call而不是用model.predict?

在這裡的例子的話,對於使用繼承 tf.keras.Model 類別建立的 Keras 模型,是把 call 方法以 @tf.function 修飾,從而轉化為 SavedModel 支持的計算圖。如果你另外建立了一個 predict 方法並且也用 @tf.function 修飾轉換成計算圖,當然也可以調用 model.predict

1 Like

请问,如何获取使用tf.saved_model.save()方法导出的.pb文件的所有节点名称呢?

pb(protocal buffer)是一个通用的序列化机制,不是TensorFlow专有的。比较直白的方式是参考Protocal Buffer的文档 https://developers.google.cn/protocol-buffers 进行操作。或者你也可以参考 https://mp.weixin.qq.com/s?__biz=MzU1OTMyNDcxMQ==&mid=2247487599&idx=1&sn=13a53532ad1d2528f0ece4f33e3ae143&chksm=fc185b27cb6fd2313992f8f2644b0a10e8dd7724353ff5e93a97d121cd1c7f3a4d4fcbcb82e8&scene=21#wechat_redirect 里面对计算图节点名字的比较。

1 Like