从Redis加载Keras模型时遇未知度量函数错误求助
问题出在哪
报错Unknown metric function: 'function',核心原因有两个:
- 只处理了自定义损失,没管自定义度量:你的模型训练时大概率用了自定义度量函数,但加载和缓存逻辑里只注册了损失函数,反序列化时找不到度量函数就报错了。
- pickle反序列化的时机错了:现在你是先把缓存里的内容用
pickle.loads解析出来,再进custom_object_scope。但Keras模型对象在被pickle反序列化的那一刻就会尝试解析内部的自定义对象,这时候作用域还没生效,自然找不到对应的度量。
怎么修复
1. 补上自定义度量的处理逻辑
首先确认你的MLModel模型有没有存储度量相关的字段(比如metric_type和metric_code),如果有,先修改批量加载模型的代码,把度量也一起处理:
# 批量加载模型时新增度量函数处理 loss_type = model_entry.loss_type loss_code = model_entry.loss # 新增:获取度量相关字段 metric_type = model_entry.metric_type metric_code = model_entry.metric_code loss_function = load_loss(loss_type, loss_code) # 新增:加载自定义度量函数 metric_function = load_metric(metric_type, metric_code) # 加载模型时同时注册损失和度量 model = tf.keras.models.load_model( local_model_path, custom_objects={loss_type: loss_function, metric_type: metric_function} ) # 保存模型详情时把度量信息也加上 model_details = { 'name': model_name, 'model': model, 'list_of_words': list_of_words, 'ml_utils_code': ml_utils_code, 'loss_type': loss_type, 'loss_code': loss_code, 'metric_type': metric_type, # 新增 'metric_code': metric_code # 新增 }
如果MLModel里没存度量信息,得先补上字段,或者加载模型后用model.metrics_names提取相关信息。
2. 调整反序列化顺序,先开作用域再解析pickle
现在的逻辑是先解析再开作用域,完全搞反了。必须先把自定义对象注册到作用域里,再去解析缓存里的模型数据,这里有两种方案:
方案一:拆分缓存存储(推荐)
把模型的元数据(名称、损失/度量类型等)和模型对象分开存到Redis,先加载元数据拿到自定义对象类型,再开作用域加载模型:
批量加载时修改存储逻辑:
# 拆分元数据和模型对象 meta_details = { 'name': model_name, 'list_of_words': list_of_words, 'ml_utils_code': ml_utils_code, 'loss_type': loss_type, 'loss_code': loss_code, 'metric_type': metric_type, 'metric_code': metric_code } # 存元数据 redis_client.set(f"model_meta:{model_title}", pickle.dumps(meta_details)) # 存模型对象 redis_client.set(f"model_obj:{model_title}", pickle.dumps(model))
修改get_loaded_model函数:
def get_loaded_model(title): try: # 先加载元数据 meta_data = redis_client.get(f"model_meta:{title}") if not meta_data: logger.warning(f"模型 '{title}' 的元数据找不到") return None meta_details = pickle.loads(meta_data) # 加载自定义损失和度量 loss_type = meta_details['loss_type'] loss_code = meta_details['loss_code'] metric_type = meta_details['metric_type'] metric_code = meta_details['metric_code'] loss_function = load_loss(loss_type, loss_code) metric_function = load_metric(metric_type, metric_code) # 先开作用域,再加载模型对象 with custom_object_scope({loss_type: loss_function, metric_type: metric_function}): model_data = redis_client.get(f"model_obj:{title}") if not model_data: logger.warning(f"模型 '{title}' 的对象找不到") return None model = pickle.loads(model_data) # 组装返回的模型详情 model_details = meta_details.copy() ml_utils_code = model_details['ml_utils_code'] ml_utils = load_ml_utils(ml_utils_code) model_details['ml_utils'] = ml_utils return model_details, model except Exception as e: logger.error(f"加载模型 '{title}' 出错: {e}") return None
方案二:临时应急(不修改存储逻辑)
如果暂时改不了存储逻辑,可以直接从数据库里查模型的元数据,再开作用域解析缓存:
def get_loaded_model(title): try: model_data = redis_client.get(f"model:{title}") if model_data: # 从数据库查模型的元数据 model_entry = MLModel.objects.get(title=title) loss_type = model_entry.loss_type loss_code = model_entry.loss metric_type = model_entry.metric_type metric_code = model_entry.metric_code # 加载自定义函数 loss_function = load_loss(loss_type, loss_code) metric_function = load_metric(metric_type, metric_code) # 先开作用域,再解析整个缓存内容 with custom_object_scope({loss_type: loss_function, metric_type: metric_function}): model_details = pickle.loads(model_data) # 加载工具类 ml_utils_code = model_details['ml_utils_code'] ml_utils = load_ml_utils(ml_utils_code) model_details['ml_utils'] = ml_utils return model_details, model_details['model'] else: logger.warning(f"模型 '{title}' 不在缓存里") return None except Exception as e: logger.error(f"加载模型 '{title}' 出错: {e}") return None
踩坑提醒
- Keras模型的反序列化(不管是pickle还是load_model)必须在
custom_object_scope生效的时候做,晚了没用。 - 只要是自定义的东西(损失、度量、自定义层),都得注册到作用域里,漏一个就报错。
- 直接pickle整个Keras模型容易出兼容性问题,比如TensorFlow版本变更后可能加载失败,推荐用SavedModel格式存模型,缓存模型路径会更稳妥。
内容的提问来源于stack exchange,提问作者Saif
相关产品推荐
相关产品推荐

