def create_feature_map(shift_x=0, shift_y=0):
"""Create a sample feature map with a pattern"""
feature_map = np.random.rand(16, 16) * 0.2 # Background noise
# Add a strong pattern
x, y = 6 + shift_x, 6 + shift_y
feature_map[x:x+4, y:y+4] = np.array([
[0.3, 0.8, 0.9, 0.4],
[0.9, 1.0, 0.9, 0.8],
[0.8, 0.9, 1.0, 0.7],
[0.3, 0.6, 0.7, 0.3]
])
return feature_map
def apply_pooling(feature_map, pool_size, pool_type):
"""Apply pooling operation"""
h, w = feature_map.shape
out_h = h // pool_size
out_w = w // pool_size
output = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
region = feature_map[i*pool_size:(i+1)*pool_size,
j*pool_size:(j+1)*pool_size]
if pool_type == 'Max':
output[i, j] = np.max(region)
else: # Average
output[i, j] = np.mean(region)
return output
def visualize_pooling(pool_type, pool_size, shift_x, shift_y, show_original):
# Create feature map
original_map = create_feature_map(0, 0)
shifted_map = create_feature_map(shift_x, shift_y)
# Apply pooling
original_pooled = apply_pooling(original_map, pool_size, pool_type)
shifted_pooled = apply_pooling(shifted_map, pool_size, pool_type)
# Calculate difference
difference = np.abs(original_pooled - shifted_pooled)
avg_diff = np.mean(difference)
# Visualization
if show_original:
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# Original - before pooling
im1 = axes[0, 0].imshow(original_map, cmap='viridis', vmin=0, vmax=1)
axes[0, 0].set_title('Original Feature Map\n(No Shift)', fontsize=12, fontweight='bold')
axes[0, 0].axis('off')
plt.colorbar(im1, ax=axes[0, 0], fraction=0.046)
# Original - after pooling
im2 = axes[1, 0].imshow(original_pooled, cmap='viridis', vmin=0, vmax=1)
axes[1, 0].set_title(f'After {pool_type} Pooling\n{original_pooled.shape[0]}ร{original_pooled.shape[1]}',
fontsize=12, fontweight='bold')
axes[1, 0].axis('off')
plt.colorbar(im2, ax=axes[1, 0], fraction=0.046)
# Shifted - before pooling
im3 = axes[0, 1].imshow(shifted_map, cmap='viridis', vmin=0, vmax=1)
axes[0, 1].set_title(f'Shifted Feature Map\nShift: ({shift_x}, {shift_y})',
fontsize=12, fontweight='bold')
axes[0, 1].axis('off')
plt.colorbar(im3, ax=axes[0, 1], fraction=0.046)
# Shifted - after pooling
im4 = axes[1, 1].imshow(shifted_pooled, cmap='viridis', vmin=0, vmax=1)
axes[1, 1].set_title(f'After {pool_type} Pooling\n{shifted_pooled.shape[0]}ร{shifted_pooled.shape[1]}',
fontsize=12, fontweight='bold')
axes[1, 1].axis('off')
plt.colorbar(im4, ax=axes[1, 1], fraction=0.046)
# Difference map
im5 = axes[0, 2].imshow(difference, cmap='hot', vmin=0, vmax=0.5)
axes[0, 2].set_title(f'Absolute Difference\nAvg: {avg_diff:.4f}',
fontsize=12, fontweight='bold')
axes[0, 2].axis('off')
plt.colorbar(im5, ax=axes[0, 2], fraction=0.046)
# Statistics
axes[1, 2].axis('off')
stats_text = f"""
๐ฏ Translation Invariance Test
Input size: 16ร16
Pool size: {pool_size}ร{pool_size}
Output size: {original_pooled.shape[0]}ร{original_pooled.shape[1]}
Dimension reduction:
{100*(1 - original_pooled.size/original_map.size):.1f}%
โโโโโโโโโโโโโโโโโโโโโ
Pattern shift: ({shift_x}, {shift_y})
Average difference after pooling:
{avg_diff:.4f}
"""
if avg_diff < 0.05:
stats_text += "\nโ
Very similar!\nPooling provides\ntranslation invariance!"
color = 'lightgreen'
elif avg_diff < 0.15:
stats_text += "\nโ ๏ธ Some difference\nbut mostly preserved"
color = 'lightyellow'
else:
stats_text += "\nโ Significant difference\nLarge shift affects output"
color = 'lightcoral'
axes[1, 2].text(0.1, 0.9, stats_text, transform=axes[1, 2].transAxes,
fontsize=10, verticalalignment='top', family='monospace',
bbox=dict(boxstyle='round', facecolor=color, alpha=0.5))
else:
# Simplified view - just show pooling effect
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Input
im1 = axes[0].imshow(shifted_map, cmap='viridis', vmin=0, vmax=1)
axes[0].set_title(f'Input Feature Map\n16ร16', fontsize=12, fontweight='bold')
axes[0].axis('off')
# Draw pooling windows
for i in range(0, 16, pool_size):
for j in range(0, 16, pool_size):
rect = patches.Rectangle((j-0.5, i-0.5), pool_size, pool_size,
linewidth=1.5, edgecolor='red', facecolor='none')
axes[0].add_patch(rect)
plt.colorbar(im1, ax=axes[0], fraction=0.046)
# Arrow
axes[1].text(0.5, 0.5, f'{pool_type}\nPooling\n{pool_size}ร{pool_size}',
transform=axes[1].transAxes, fontsize=16, fontweight='bold',
ha='center', va='center',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.7))
axes[1].axis('off')
# Output
im2 = axes[2].imshow(shifted_pooled, cmap='viridis', vmin=0, vmax=1)
axes[2].set_title(f'Output Feature Map\n{shifted_pooled.shape[0]}ร{shifted_pooled.shape[1]}',
fontsize=12, fontweight='bold')
axes[2].axis('off')
plt.colorbar(im2, ax=axes[2], fraction=0.046)
plt.tight_layout()
plt.show()
print("๐ Pooling Effect Visualizer")
print("="*50)
print("See how pooling reduces dimensions and provides translation invariance!\n")
interact(visualize_pooling,
pool_type=widgets.Dropdown(options=['Max', 'Average'], value='Max',
description='Pool Type:',
style={'description_width': '120px'}),
pool_size=widgets.Dropdown(options=[2, 4], value=2,
description='Pool Size:',
style={'description_width': '120px'}),
shift_x=widgets.IntSlider(value=0, min=-2, max=2, description='Shift X:',
style={'description_width': '120px'}),
shift_y=widgets.IntSlider(value=0, min=-2, max=2, description='Shift Y:',
style={'description_width': '120px'}),
show_original=widgets.Checkbox(value=True, description='Show comparison',
style={'description_width': '120px'}));