YOLOv3 TensorFlow训练调用mAP计算时出现InaccessibleTensorError报错
问题根因
你的代码独立运行时处于Eager模式,允许Python原生的列表、for循环操作张量,但model.fit的验证逻辑会被自动封装进tf.function图模式执行,图模式下Python的控制流(普通for、if、列表append张量)会产生作用域隔离,具体问题点如下:
filter_predictions函数的if max_prediction < len(det_probs_this_inst):分支只写了if内的返回逻辑,没有写else分支的返回值,图模式下cond两个分支的返回结构必须完全一致,否则张量会被创建在if分支的子图里,外部访问不到- 两层Python原生for循环(遍历类别、遍历nms结果的keep)+ Python列表存张量的写法,在图模式下会被转换为
tf.while_loop,每个循环迭代都是子图,append到Python列表里的张量属于子图作用域,外层代码无法访问 - 外层遍历batch的
for i in tf.range(num_images)用的是Python for套tf.range,也会触发图模式下的循环作用域隔离问题
解决方案
方案1:全量改造为图模式兼容写法(推荐)
第一步:改造filter_predictions函数,全用TensorFlow原生API替换Python控制流
def filter_predictions(localizations , det_probs_this_inst , det_class_this_inst , num_classes, max_prediction=100, iou_threshold=0.5, score_threshold=0.001): # 所有输入都是单张图的张量,无batch维度 det_probs_this_inst = tf.squeeze(det_probs_this_inst) det_class_this_inst = tf.squeeze(det_class_this_inst) # 先取分数topk,统一分支逻辑,避免if分支返回结构不一致 k = tf.minimum(max_prediction, tf.shape(det_probs_this_inst)[0]) pred_order = tf.argsort(det_probs_this_inst, direction='DESCENDING')[:k] locs = tf.gather(localizations, pred_order, axis=0) probs = tf.gather(det_probs_this_inst, pred_order) cls_idx = tf.gather(det_class_this_inst, pred_order) # 按类别做NMS,用tf.map_fn替换Python for循环 def per_class_nms(c): # 取当前类的所有框和分数 mask = tf.equal(cls_idx, c) class_locs = tf.boolean_mask(locs, mask) class_probs = tf.boolean_mask(probs, mask) # 调用tf原生NMS keep = tf.image.non_max_suppression(class_locs, class_probs, max_output_size=k, iou_threshold=iou_threshold, score_threshold=score_threshold) # 返回当前类保留的框、分数、类别 return tf.gather(class_locs, keep), tf.gather(class_probs, keep), tf.fill((tf.shape(keep)[0],), c) # 遍历所有类别 all_boxes, all_probs, all_classes = tf.map_fn( per_class_nms, tf.range(num_classes), fn_output_signature=( tf.TensorSpec(shape=(None,4), dtype=tf.float32), tf.TensorSpec(shape=(None,), dtype=tf.float32), tf.TensorSpec(shape=(None,), dtype=tf.int64) ) ) # 合并所有类的结果,拍平维度 final_boxes = tf.reshape(all_boxes, (-1,4)) final_probs = tf.reshape(all_probs, (-1,1)) final_class = tf.reshape(all_classes, (-1,1)) return final_boxes, final_probs, final_class
第二步:改造外层batch遍历逻辑
不要用Python for遍历tf.range(num_images),改用tf.map_fn批量处理整个batch的所有样本,避免循环作用域问题:
# 定义单张图的后处理逻辑 def process_single_image(args): adjusted_bbox, det_prob, det_cls = args final_boxes, final_probs, final_class = filter_predictions(adjusted_bbox, det_prob, det_cls, num_classes) # 过滤低分数框 mask = tf.squeeze(final_probs > filter_threshold) final_boxes = tf.boolean_mask(final_boxes, mask) final_probs = tf.boolean_mask(final_probs, mask) final_class = tf.boolean_mask(final_class, mask) return gen_box_objects(final_boxes, final_probs, final_class) # 批量处理整个batch,代替Python for循环 pred_box_objects = tf.map_fn( process_single_image, elems=(adjusted_bbox_delta, det_probs, det_class), fn_output_signature=tf.TensorSpec(shape=(None, 6), dtype=tf.float32) # 此处要和gen_box_objects的输出类型、维度匹配 )
方案2:临时快速验证方案
如果只是验证阶段算mAP,不需要图模式加速的话,可以在自定义mAP指标的update_state方法上加@tf.autograph.experimental.do_not_convert装饰器,强制这部分逻辑用Eager模式执行,就不会有图作用域问题,不需要修改原有后处理逻辑:
class mAP(tf.keras.metrics.Metric): # 其他原有代码 @tf.autograph.experimental.do_not_convert def update_state(self, y_true, y_pred, sample_weight=None): # 你原来的后处理逻辑直接放这里即可
后续排查注意事项
- 所有在
model.fit里执行的自定义逻辑,优先用TensorFlow原生API,不要混用Python的控制流和列表存张量 - 图模式下if/else的两个分支返回的张量数量、维度、类型必须完全一致
- 出现
InaccessibleTensorError首先排查是不是有张量在子图(if分支、循环迭代)里创建,被外层作用域访问
内容的提问来源于stack exchange,提问作者yogeesh agarwal
相关产品推荐
相关产品推荐

