将Python众数下采样代码迁移至TensorFlow并解决AutoGraph报错
Got it, let's tackle this problem step by step. You need to port your numpy-based mode downsampling function to TensorFlow while fixing the autograph conversion errors. Here's a complete, working solution along with explanations for why your original approach was causing issues:
First, Understand the Autograph Error Root Cause
Your original Python code uses nested for loops with dynamic iteration counts (since pro1 and pro2 depend on runtime tensor shapes). Autograph struggles with standard Python for loops when the loop count isn't statically known—instead, we need to use TensorFlow's tf.while_loop which is designed for dynamic loop logic and plays nicely with autograph.
Additionally, numpy operations like np.bincount and direct tensor indexing assignment won't work in a TensorFlow graph context; we need to replace them with TF-native APIs.
Complete TensorFlow Implementation with Autograph Compatibility
Here's the full most_down function, built to handle the [H, W, C] input shape and work with @autograph.convert():
import tensorflow as tf from tensorflow.python.autograph import convert @convert() def most_down(map_tensor, core): # Convert inputs to tf.int32 tensors if not already map_tensor = tf.convert_to_tensor(map_tensor, dtype=tf.int32) core = tf.convert_to_tensor(core, dtype=tf.int32) # Unpack core dimensions (block height, block width) block_h, block_w = tf.unstack(core) # Get input feature map shape (height, width, channels) h, w, c = tf.unstack(tf.shape(map_tensor)) # Calculate output dimensions (pro1 = downsampled height, pro2 = downsampled width) pro1 = tf.cast(tf.cast(h, tf.float32) / tf.cast(block_h, tf.float32), tf.int32) pro2 = tf.cast(tf.cast(w, tf.float32) / tf.cast(block_w, tf.float32), tf.int32) # Adjust dimensions if there's a remainder (like your original numpy code) pro1 = tf.cond(tf.not_equal(h % block_h, 0), lambda: pro1 + 1, lambda: pro1) pro2 = tf.cond(tf.not_equal(w % block_w, 0), lambda: pro2 + 1, lambda: pro2) # Initialize empty output tensor new_map = tf.zeros([pro1, pro2, c], dtype=tf.int32) # Define inner loop for width direction (j) def loop_j(j, current_map): # Calculate block boundaries for height start_h = i * block_h end_h = tf.cond(tf.less(i, pro1 - 1), lambda: (i + 1) * block_h, lambda: h) # Calculate block boundaries for width start_w = j * block_w end_w = tf.cond(tf.less(j, pro2 - 1), lambda: (j + 1) * block_w, lambda: w) # Extract the current block and flatten it (preserving channels) block = map_tensor[start_h:end_h, start_w:end_w, :] block_flat = tf.reshape(block, [-1, c]) # Helper function to compute mode for a single channel def compute_channel_mode(channel_data): # Get unique values and their occurrence counts unique_vals, _, counts = tf.unique_with_counts(channel_data) # Find the value with the highest count max_count_idx = tf.argmax(counts, output_type=tf.int32) return unique_vals[max_count_idx] # Compute mode for all channels using map_fn channel_modes = tf.map_fn(compute_channel_mode, tf.transpose(block_flat), dtype=tf.int32) # Update the output tensor at position (i,j) with the computed modes current_map = tf.tensor_scatter_nd_update( current_map, indices=[[i, j]], updates=[channel_modes] ) return j + 1, current_map # Define outer loop for height direction (i) def loop_i(i, current_map): # Run the inner width loop j_start = tf.constant(0, dtype=tf.int32) _, updated_map = tf.while_loop( cond=lambda j, _: tf.less(j, pro2), body=loop_j, loop_vars=[j_start, current_map] ) return i + 1, updated_map # Run the outer height loop i_start = tf.constant(0, dtype=tf.int32) _, final_map = tf.while_loop( cond=lambda i, _: tf.less(i, pro1), body=loop_i, loop_vars=[i_start, new_map] ) return final_map
Test the Function
Let's verify it works with your sample input:
# Sample input with channel dimension (required for your input spec) map_sample = [[0,0,1,1,4], [0,1,2,1,4], [2,2,0,0,3]] map_tensor = tf.convert_to_tensor(map_sample, dtype=tf.int32)[..., tf.newaxis] # Shape [3,5,1] core_tensor = tf.convert_to_tensor([2,2], dtype=tf.int32) # Run the downsampling result = most_down(map_tensor, core_tensor) print(tf.squeeze(result).numpy()) # Output should match your expected result: # [[0 1 4] # [2 0 3]]
Key Fixes for Autograph Compatibility
- Replace Python loops with
tf.while_loop: Autograph can't handle dynamic Pythonforloops, buttf.while_loopis designed for graph-compatible dynamic iteration. - Use TF-native operations: Replaced
np.bincountwithtf.unique_with_counts(more flexible for unknown value ranges) andtf.reshapeinstead ofnp.reshape. - Immutable tensor updates: TensorFlow tensors are immutable, so we use
tf.tensor_scatter_nd_updateinstead of direct indexing assignment (new_map[i,j] = ...). - Channel-wise processing: Used
tf.map_fnto compute the mode for each channel individually, which works seamlessly in a graph context. - TF2 API compliance: Replaced deprecated
tf.to_int32withtf.castto avoid compatibility warnings.
内容的提问来源于stack exchange,提问作者Tina Liu

