LONG POST: I know many posts are available on StackOverflow on a similar topic, but none worked for me so far. The animation is either not fast enough, or the text is animated (not how I want), but the plot isn't. Any help is appreciated.
Hello,
I am implementing self-organizing maps and would like to create a gif file of the changing weights and plot text i.e. title.
X, Y = np.meshgrid(np.arange(w.shape[0]), np.arange(w.shape[1]))
pos = np.array((X,Y))
som = SelfOrganizingMap(grid_pos=pos)
data = np.random.uniform(0, 1, (100, 3))
num_dims = data.shape[-1]
n_neuron = 5*np.sqrt(data.shape[0])
dim = int(np.ceil(np.sqrt(n_neuron)))
w = np.random.uniform(np.min(data), np.max(data), (dim, dim, num_dims))
for step in range(max_steps):
for t in range(len(data)):
euclidean_distance = som.e_distance(weights = w, x = data[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += learning_rate * influence_radius[:, :, None] * (data[t] - w) #create gif of this
The parameters are as follows:
So far I have tried the following:
Try #1
@gif.frame
def animate(weight):
plt.imshow(weight)
plt.axis('off')
and then in the main code:
frames = []
gif.options.matplotlib["dpi"] = 300
for step in range(max_steps):
for t in range(len(data)):
euclidean_distance = som.e_distance(weights = w, x = data[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += learning_rate * influence_radius[:, :, None] * (data[t] - w)
frames.append(animate(weight = w))
gif.save(frames, "color_som.gif", duration=15)
The execution is killed because of the memory issue.
Try #2:
def animate(weight):
text = axes.text(0.5,1.05, 'Iteration: {}'.format(step), horizontalalignment='center', verticalalignment='bottom',transform=axes.transAxes)
im = axes.imshow(weight)
axes.axis('off')
frames.append([im, text])
return frames
and then in the main code:
plt.rcParams["font.family"] = "Times New Roman"
plt.rcParams.update({'font.size': 14})
fig, axes = plt.subplots()
for step in range(max_steps):
for t in range(len(data)):
euclidean_distance = som.e_distance(weights = w, x = data[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += learning_rate * influence_radius[:, :, None] * (data[t] - w)
images = animate(weight = w)
ani = animation.ArtistAnimation(fig, images, interval=1, blit=False, repeat_delay=50)
ani.save("./color_som.gif", writer='pillow', fps=30)
This works fine if max_steps is set to lower values but is extremely slow if it is set to higher values. The execution time for max_steps = 50 is 250.62131214141846
Try #3:
# initialize elements to be animated
plt.rcParams["font.family"] = "Times New Roman"
plt.rcParams.update({'font.size': 14})
fig, axes = plt.subplots()
w_plot = axes.imshow(w)
axes.axis('off')
def update_data(t, lr, x, w, w_plot):
euclidean_distance = som.e_distance(weights=w, x=x[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += lr * influence_radius[:, :, None] * (x[t] - w)
w_plot.set_data(w)
step_txt.set_text("step: {}".format(t))
step_txt = fig.text(0.5, 0.95, "step: 0", ha="center", weight="bold")
anim = FuncAnimation(fig, func = partial(update_data, lr = 0.1, x=data, w=w, w_plot= w_plot), frames= len(data), interval=300, repeat_delay=500)
anim.save("test.gif")
This works and is pretty fast (execution time=3.0469424724578857), but this is not what I want because:
for t in range(len(data)):
I want animation with double for loop executed
Try #4
I also tried this "Creating matplotlib animation from plt.imshow in triple for loop",
# initialize elements to be animated
plt.rcParams["font.family"] = "Times New Roman"
plt.rcParams.update({'font.size': 14})
fig, axes = plt.subplots()
w_plot = axes.imshow(w)
step_txt = fig.text(0.5, 0.95, "step: 0", ha="center", weight="bold")
def update_data(t):
w_plot.set_data(wei[t])
step_txt.set_text("step: {}".format(t))
weight = []
for step in range(max_steps):
for t in range(len(data)):
euclidean_distance = som.e_distance(weights = w, x = data[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += learning_rate * influence_radius[:, :, None] * (data[t] - w)
weight.append(w)
anim = FuncAnimation(fig, func = update_data, frames= len(data), interval=50, repeat_delay=500) #, max_steps= max_steps #len(data)
anim.save("test.gif")#, writer="pillow")
This separates the calculation from the animation and executes double "for" loop.
Also, when I save the animation as gif, the animation is on the text but not the plot itself. It takes only the final plot. Also, I want step text to change from 0,1,....499 (for max_steps = 500) instead of step = 0,1...99 (i.e. len(data))
I also tried celluloid package in python, it did not work for me.
How to get a gif image of the weights with both the for loop executed and text change to step values (i.e. outer loop value) instead of t value (i.e. inner for loop)?
So, the solution is:
# Initialize elements to be animated
fig, axes = plt.subplots()
w_plot = axes.imshow(w)
step_txt = fig.text(0.5, 0.95, "step: 0", ha="center", weight="bold")
def update_data(t):
w_plot.set_data(wei[t])
if t%len(data)==0:
step_txt.set_text("step: {}".format(np.divmod(t, len(data))[0]))
weight = []
for step in range(max_steps):
for t in range(len(data)):
euclidean_distance = som.e_distance(weights = w, x = data[t])
distance_node, bmu = som.winning_neuron(euclidean_distance)
influence_radius = som.decay(distance_node)
w += learning_rate * influence_radius[:, :, None] * (data[t] - w)
weight.append(w.tolist()
weight = np.array(weight)
anim = FuncAnimation(fig, func = update_data, frames= max_steps*len(data), interval=50, repeat_delay=500) #, max_steps= max_steps #len(data)
anim.save("test.gif")
The gif file introduces some static in the animation. For that, you can either specify fps or dpi or both, but it will increase the execution time, and if max_steps is high, then it will take a lot of time. Alternatively, one can save the animation as .mp4 file. It doesn't introduce any static in the animation.
anim.save("test.mp4")