mirror of
https://github.com/helblazer811/ManimML.git
synced 2026-03-13 08:41:18 +08:00
Readme changes, logo added, tags in readme
This commit is contained in:
committed by
Alec Helbling
parent
ca0e93d657
commit
4eb5296c9c
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Alec Helbling
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@ setup:
|
||||
conda activate manim
|
||||
export PROJECT_ROOT=$(pwd)
|
||||
video:
|
||||
manim -pqh src/vae.py VAEScene --media_dir media
|
||||
manim -pqh src/variational_autoencoder.py VAEScene --media_dir media
|
||||
cp media/videos/vae/720p60/VAEScene.mp4 examples
|
||||
train:
|
||||
cd src/autoencoder_models
|
||||
|
||||
37
Readme.md
37
Readme.md
@@ -1,26 +1,43 @@
|
||||
# Manim Machine Learning
|
||||
<a href="https://github.com/helblazer811/ManimMachineLearning">
|
||||
<img src="examples/ManimMLLogo.gif">
|
||||
</a>
|
||||
|
||||
Manim Machine Learning is a project focused on providing animations and visualizations of common machine learning concepts with the [Manim Community Library](https://www.manim.community/). We want this project to be a compilation of primitive visualizations that can be easily combined to create videos about complex machine learning concepts.
|
||||
[](https://github.com/helblazer811/ManimMachineLearning/blob/main/LICENSE.md)
|
||||
[](https://img.shields.io/github/v/release/helblazer811/ManimMachineLearning)
|
||||
[](https://GitHub.com/helblazer811/ManimMachineLearning/releases/)
|
||||
[](https://twitter.com/alec_helbling)
|
||||
|
||||
Manim Machine Learning is a project focused on providing animations and visualizations of common machine learning concepts with the [Manim Community Library](https://www.manim.community/). We want this project to be a compilation of primitive visualizations that can be easily combined to create videos about complex machine learning concepts. Additionally, we want to provide a set of abstractions which allow users to focus on explanations instead of software engineering.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [Examples](#examples)
|
||||
|
||||
## Getting Started
|
||||
First you will want to install manim. Then you can run the following to generate the example videos.
|
||||
|
||||
`make video`
|
||||
|
||||
or
|
||||
First you will want to [install manim](https://docs.manim.community/en/stable/installation.html). Then you can run the following to generate the example videos.
|
||||
|
||||
`manim -pqh src/vae.py VAEScene`
|
||||
|
||||
## Examples
|
||||
|
||||
Checkout the ```examples``` directory for some example videos with source code.
|
||||
|
||||
### Variational Autoencoders
|
||||
|
||||
This is a visualization of a Variational Autoencoder. You can also find a video form in examples/
|
||||
This is a visualization of a Variational Autoencoder.
|
||||
|
||||
<img src="examples/VAEImage.png" width="600">
|
||||
<img src="examples/VAEScene.gif" width="600">
|
||||
|
||||
### VAE Disentanglement
|
||||
|
||||
This is a visualization of disentanglement with a Variational Autoencoder
|
||||
|
||||
<img src="examples/DisentanglementScene.gif" width="600">
|
||||
|
||||
### Neural Networks
|
||||
|
||||
This is a visualization of a Neural Network. You can find a video animation of a neural network in examples/
|
||||
This is a visualization of a Neural Network.
|
||||
|
||||
<img src="examples/NNImage.png" width="600">
|
||||
<img src="examples/TestNeuralNetworkScene.gif" width="600">
|
||||
|
||||
BIN
examples/ManimMLLogo.gif
Normal file
BIN
examples/ManimMLLogo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
@@ -10,7 +10,7 @@ import scipy
|
||||
import scipy.stats
|
||||
import cv2
|
||||
|
||||
def binned_images(model_path, num_x_bins=10, plot=False):
|
||||
def binned_images(model_path, num_x_bins=6, plot=False):
|
||||
latent_dim = 2
|
||||
model = load_vae_from_path(model_path, latent_dim)
|
||||
image_dataset = load_dataset(digit=2)
|
||||
|
||||
Binary file not shown.
@@ -198,7 +198,7 @@ def train_model(latent_dim=16, plot=True, digit=1, epochs=200):
|
||||
losses.append(loss.detach().cpu())
|
||||
outputs.append((epochs, image, reconstructed))
|
||||
|
||||
torch.save(model.state_dict(), f"saved_models/model_dim{latent_dim}.pth")
|
||||
torch.save(model.state_dict(), os.path.join(os.environ["PROJECT_ROOT"], f"saved_models/model_dim{latent_dim}.pth"))
|
||||
|
||||
if plot:
|
||||
# Defining the Plot Style
|
||||
@@ -211,4 +211,4 @@ def train_model(latent_dim=16, plot=True, digit=1, epochs=200):
|
||||
plt.show()
|
||||
|
||||
if __name__ == "__main__":
|
||||
train_model(latent_dim=2, digit=1, epochs=40)
|
||||
train_model(latent_dim=2, digit=2, epochs=40)
|
||||
|
||||
@@ -19,7 +19,7 @@ class VAEDecoder(VGroup):
|
||||
|
||||
class DisentanglementVisualization(VGroup):
|
||||
|
||||
def __init__(self, model_path="autoencoder_models/saved_models/model_dim2.pth", image_height=0.2):
|
||||
def __init__(self, model_path="autoencoder_models/saved_models/model_dim2.pth", image_height=0.35):
|
||||
self.model_path = model_path
|
||||
self.image_height = image_height
|
||||
# Load disentanglement image objects
|
||||
@@ -33,7 +33,7 @@ class DisentanglementVisualization(VGroup):
|
||||
r, c = self.image_handler["bin_indices"][image_index]
|
||||
# Move the image to the correct location
|
||||
r_offset = -1.2
|
||||
c_offset = 0.2
|
||||
c_offset = 0.25
|
||||
image_location = [c_offset + c*self.image_height, r_offset + r*self.image_height, 0]
|
||||
image_mobject.move_to(image_location)
|
||||
animation_list.append(FadeIn(image_mobject))
|
||||
@@ -54,7 +54,7 @@ class DisentanglementScene(Scene):
|
||||
embedding = VGroup()
|
||||
# Sample points from a Gaussian
|
||||
num_points = 200
|
||||
standard_deviation = [0.6, 1.0]
|
||||
standard_deviation = [0.6, 0.8]
|
||||
mean = [0, 0]
|
||||
points = np.random.normal(mean, standard_deviation, size=(num_points, 2))
|
||||
# Make an axes
|
||||
@@ -78,13 +78,13 @@ class DisentanglementScene(Scene):
|
||||
def construct(self):
|
||||
# Make the VAE decoder
|
||||
vae_decoder = VAEDecoder()
|
||||
vae_decoder.shift([-0.65, 0, 0])
|
||||
vae_decoder.shift([-0.55, 0, 0])
|
||||
self.play(Create(vae_decoder), run_time=1)
|
||||
# Make the embedding
|
||||
embedding = self._construct_embedding()
|
||||
embedding.scale(0.8)
|
||||
embedding.scale(0.9)
|
||||
embedding.move_to(vae_decoder.get_left())
|
||||
embedding.shift([-0.7, 0, 0])
|
||||
embedding.shift([-0.85, 0, 0])
|
||||
self.play(Create(embedding))
|
||||
# Make disentanglment visualization
|
||||
disentanglement = DisentanglementVisualization()
|
||||
|
||||
57
src/interpolation.py
Normal file
57
src/interpolation.py
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
"""Visualization of VAE Interpolation"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.environ["PROJECT_ROOT"])
|
||||
from manim import *
|
||||
import pickle
|
||||
import numpy as np
|
||||
import neural_network
|
||||
import variational_autoencoder
|
||||
|
||||
"""
|
||||
The VAE Scene for the twitter video.
|
||||
"""
|
||||
config.pixel_height = 720
|
||||
config.pixel_width = 1280
|
||||
config.frame_height = 6.0
|
||||
config.frame_width = 6.0
|
||||
# Set random seed so point distribution is constant
|
||||
np.random.seed(1)
|
||||
|
||||
class InterpolationScene(MovingCameraScene):
|
||||
"""Scene object for a Variational Autoencoder and Autoencoder"""
|
||||
|
||||
def construct(self):
|
||||
# Set Scene config
|
||||
vae = variational_autoencoder.VariationalAutoencoder(dot_radius=0.035, layer_spacing=0.5)
|
||||
vae.move_to(ORIGIN)
|
||||
vae.encoder.shift(LEFT*0.5)
|
||||
vae.decoder.shift(RIGHT*0.5)
|
||||
mnist_image_handler = variational_autoencoder.MNISTImageHandler()
|
||||
image_pair = mnist_image_handler.image_pairs[3]
|
||||
# Make forward pass animation and DO NOT run it
|
||||
forward_pass_animation = vae.make_forward_pass_animation(image_pair)
|
||||
# Make the interpolation animation
|
||||
interpolation_images = mnist_image_handler.interpolation_images
|
||||
interpolation_animation = vae.make_interpolation_animation(interpolation_images)
|
||||
embedding_zoom_animation = self.camera.auto_zoom([
|
||||
vae.embedding,
|
||||
vae.decoder,
|
||||
vae.output_image
|
||||
], margin=0.5)
|
||||
# Make animations
|
||||
forward_pass_animations = []
|
||||
for i in range(7):
|
||||
anim = vae.decoder.make_forward_propagation_animation(run_time=0.5)
|
||||
forward_pass_animations.append(anim)
|
||||
forward_pass_animation_group = AnimationGroup(*forward_pass_animations, lag_ratio=1.0)
|
||||
# Make forward pass animations
|
||||
self.play(Create(vae), run_time=1.5)
|
||||
self.play(FadeOut(vae.encoder), run_time=1.0)
|
||||
self.play(embedding_zoom_animation, run_time=1.5)
|
||||
interpolation_animation = AnimationGroup(
|
||||
forward_pass_animation_group,
|
||||
interpolation_animation
|
||||
)
|
||||
self.play(interpolation_animation, run_time=9.0)
|
||||
41
src/logo.py
Normal file
41
src/logo.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Logo for Manim Machine Learning
|
||||
"""
|
||||
from manim import *
|
||||
from neural_network import NeuralNetwork
|
||||
|
||||
config.pixel_height = 500
|
||||
config.pixel_width = 1920
|
||||
config.frame_height = 10.0
|
||||
config.frame_width = 10.0
|
||||
|
||||
class ManimMLLogo(Scene):
|
||||
|
||||
def construct(self):
|
||||
self.text = Text("ManimML")
|
||||
self.text.scale(1.3)
|
||||
self.neural_network = NeuralNetwork([3, 5, 3, 6, 3], layer_spacing=0.6, node_color=BLUE)
|
||||
self.neural_network.scale(0.8)
|
||||
self.neural_network.move_to(self.text.get_right())
|
||||
self.neural_network.shift(RIGHT * 1.3)
|
||||
self.logo_group = VGroup(self.text, self.neural_network)
|
||||
self.logo_group.scale(1.5)
|
||||
self.logo_group.move_to(ORIGIN)
|
||||
self.play(Write(self.text))
|
||||
self.play(Create(self.neural_network))
|
||||
# self.surrounding_rectangle = SurroundingRectangle(self.logo_group, buff=0.3, color=BLUE)
|
||||
underline = Underline(self.text, color=BLUE)
|
||||
animation_group = AnimationGroup(
|
||||
self.neural_network.make_forward_propagation_animation(run_time=5),
|
||||
Create(underline),
|
||||
# Create(self.surrounding_rectangle)
|
||||
)
|
||||
# self.surrounding_rectangle = SurroundingRectangle(self.logo_group, buff=0.3, color=BLUE)
|
||||
underline = Underline(self.text, color=BLUE)
|
||||
animation_group = AnimationGroup(
|
||||
self.neural_network.make_forward_propagation_animation(run_time=5),
|
||||
Create(underline),
|
||||
# Create(self.surrounding_rectangle)
|
||||
)
|
||||
self.play(animation_group)
|
||||
self.wait(5)
|
||||
@@ -84,7 +84,7 @@ class NeuralNetwork(VGroup):
|
||||
layers = VGroup()
|
||||
# Create each layer
|
||||
for layer_index, node_count in enumerate(self.layer_node_count):
|
||||
layer = NeuralNetworkLayer(node_count)
|
||||
layer = NeuralNetworkLayer(node_count, node_color=self.node_color)
|
||||
# Manage spacing
|
||||
layer.move_to([self.layer_spacing * layer_index, 0, 0])
|
||||
# Add layer to VGroup
|
||||
|
||||
20
src/probability_embedding.py
Normal file
20
src/probability_embedding.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from manim import *
|
||||
|
||||
class NeuralNetworkEmbedding(Axes):
|
||||
"""NeuralNetwork embedding object that can show probability distributions"""
|
||||
|
||||
def construct_gaussian_distribution(self, mean, covariance, color=ORANGE, dot_radius=0.05, ellipse_stroke_width=0.3):
|
||||
"""Returns a 2d Gaussian distribution object with given mean and covariance"""
|
||||
# map mean and covariance to frame coordinates
|
||||
mean = self.coords_to_point(*mean)
|
||||
covariance = self.coords_to_point(*covariance)
|
||||
# Make a covariance ellipse centered at mean
|
||||
center_dot = Dot(mean, radius=dot_radius, color=color)
|
||||
ellipse = Ellipse(width=covariance[0], height=covariance[1], color=color, fill_opacity=0.3, stroke_width=ellipse_stroke_width)
|
||||
ellipse.move_to(mean)
|
||||
gaussian_distribution = VGroup(
|
||||
center_dot,
|
||||
ellipse
|
||||
)
|
||||
|
||||
return gaussian_distribution
|
||||
@@ -4,35 +4,34 @@ In this module I define Manim visualizations for Variational Autoencoders
|
||||
and Traditional Autoencoders.
|
||||
|
||||
"""
|
||||
from configparser import Interpolation
|
||||
from random import sample
|
||||
from manim import *
|
||||
import pickle
|
||||
import numpy as np
|
||||
import os
|
||||
import neural_network
|
||||
from scipy.interpolate import make_interp_spline
|
||||
|
||||
class VariationalAutoencoder(VGroup):
|
||||
"""Variational Autoencoder Manim Visualization"""
|
||||
|
||||
def __init__(
|
||||
self, encoder_nodes_per_layer=[5, 3], decoder_nodes_per_layer=[3, 5], point_color=BLUE,
|
||||
dot_radius=0.05, ellipse_stroke_width=2.0
|
||||
dot_radius=0.05, ellipse_stroke_width=2.0, layer_spacing=0.5
|
||||
):
|
||||
super(VGroup, self).__init__()
|
||||
self.encoder_nodes_per_layer = encoder_nodes_per_layer
|
||||
self.decoder_nodes_per_layer = decoder_nodes_per_layer
|
||||
self.point_color = point_color
|
||||
self.dot_radius = dot_radius
|
||||
self.layer_spacing = layer_spacing
|
||||
self.ellipse_stroke_width = ellipse_stroke_width
|
||||
# Make the VMobjects
|
||||
self.encoder, self.decoder = self._construct_encoder_and_decoder()
|
||||
self.embedding = self._construct_embedding()
|
||||
# Setup the relative locations
|
||||
self.embedding.move_to(self.encoder)
|
||||
self.embedding.shift([1.1 * self.encoder.width, 0, 0])
|
||||
self.embedding.shift([1.4 * self.encoder.width, 0, 0])
|
||||
self.decoder.move_to(self.embedding)
|
||||
self.decoder.shift([self.decoder.width * 1.1, 0, 0])
|
||||
self.decoder.shift([self.decoder.width * 1.4, 0, 0])
|
||||
# Add the objects to the VAE object
|
||||
self.add(self.encoder)
|
||||
self.add(self.decoder)
|
||||
@@ -42,11 +41,11 @@ class VariationalAutoencoder(VGroup):
|
||||
"""Makes the VAE encoder and decoder"""
|
||||
# Make the encoder
|
||||
layer_node_count = self.encoder_nodes_per_layer
|
||||
encoder = neural_network.NeuralNetwork(layer_node_count, dot_radius=self.dot_radius)
|
||||
encoder = neural_network.NeuralNetwork(layer_node_count, dot_radius=self.dot_radius, layer_spacing=self.layer_spacing)
|
||||
encoder.scale(1.2)
|
||||
# Make the decoder
|
||||
layer_node_count = self.decoder_nodes_per_layer
|
||||
decoder = neural_network.NeuralNetwork(layer_node_count, dot_radius=self.dot_radius)
|
||||
decoder = neural_network.NeuralNetwork(layer_node_count, dot_radius=self.dot_radius, layer_spacing=self.layer_spacing)
|
||||
decoder.scale(1.2)
|
||||
|
||||
return encoder, decoder
|
||||
@@ -56,7 +55,7 @@ class VariationalAutoencoder(VGroup):
|
||||
embedding = VGroup()
|
||||
# Sample points from a Gaussian
|
||||
num_points = 200
|
||||
standard_deviation = [0.7, 0.7]
|
||||
standard_deviation = [0.9, 0.9]
|
||||
mean = [0, 0]
|
||||
points = np.random.normal(mean, standard_deviation, size=(num_points, 2))
|
||||
# Make an axes
|
||||
@@ -152,7 +151,7 @@ class VariationalAutoencoder(VGroup):
|
||||
self.input_image.move_to(self.encoder.get_left())
|
||||
self.input_image.shift(LEFT)
|
||||
self.output_image.move_to(self.decoder.get_right())
|
||||
self.output_image.shift(RIGHT)
|
||||
self.output_image.shift(RIGHT*1.3)
|
||||
# Make encoder forward pass
|
||||
encoder_forward_pass = self.encoder.make_forward_propagation_animation(run_time=per_unit_runtime)
|
||||
# Make red dot in embedding
|
||||
@@ -245,8 +244,8 @@ class MNISTImageHandler():
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_pairs_file_path="src/autoencoder_models/image_pairs.pkl",
|
||||
interpolations_file_path="src/autoencoder_models/interpolations.pkl"
|
||||
image_pairs_file_path=os.path.join(os.environ["PROJECT_ROOT"], "autoencoder_models/image_pairs.pkl"),
|
||||
interpolations_file_path=os.path.join(os.environ["PROJECT_ROOT"], "autoencoder_models/interpolations.pkl")
|
||||
):
|
||||
self.image_pairs_file_path = image_pairs_file_path
|
||||
self.interpolations_file_path = interpolations_file_path
|
||||
@@ -271,8 +270,8 @@ class MNISTImageHandler():
|
||||
"""
|
||||
config.pixel_height = 720
|
||||
config.pixel_width = 1280
|
||||
config.frame_height = 10.0
|
||||
config.frame_width = 10.0
|
||||
config.frame_height = 5.0
|
||||
config.frame_width = 5.0
|
||||
# Set random seed so point distribution is constant
|
||||
np.random.seed(1)
|
||||
|
||||
Reference in New Issue
Block a user