Added GAN visualization.

This commit is contained in:
Alec Helbling
2022-04-22 19:08:28 -04:00
parent ffd31701bf
commit 0152be64b0
24 changed files with 530 additions and 154 deletions

View File

@ -31,43 +31,40 @@ Checkout the ```examples``` directory for some example videos with source code.
### Neural Networks ### Neural Networks
This is a visualization of a Neural Network made using ManimML. It has a Pytorch style list of layers that can be composed in arbitrary order. The following video is made with the code from below. This is a visualization of a Variational Autoencoder made using ManimML. It has a Pytorch style list of layers that can be composed in arbitrary order. The following video is made with the code from below.
<img src="examples/media/ImageNeuralNetworkScene.gif"> <img src="examples/media/VAEScene.gif">
```python ```python
from manim import * class VariationalAutoencoderScene(Scene):
from manim_ml.neural_network.layers import FeedForwardLayer, ImageLayer
from manim_ml.neural_network.neural_network import NeuralNetwork
from PIL import Image
import numpy as np
class ImageNeuralNetworkScene(Scene):
def construct(self): def construct(self):
embedding_layer = EmbeddingLayer(dist_theme="ellipse").scale(2)
image = Image.open('images/image.jpeg') image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image) numpy_image = np.asarray(image)
# Make nn # Make nn
layers = [ neural_network = NeuralNetwork([
ImageLayer(numpy_image, height=1.0), ImageLayer(numpy_image, height=1.4),
FeedForwardLayer(3),
FeedForwardLayer(5), FeedForwardLayer(5),
FeedForwardLayer(3) FeedForwardLayer(3),
] embedding_layer,
nn = NeuralNetwork(layers) FeedForwardLayer(3),
# Center the nn FeedForwardLayer(5),
nn.move_to(ORIGIN) ImageLayer(numpy_image, height=1.4),
self.add(nn) ], layer_spacing=0.1)
# Play animation
self.play(nn.make_forward_pass_animation()) neural_network.scale(1.3)
self.play(Create(neural_network))
self.play(neural_network.make_forward_pass_animation(run_time=15))
``` ```
### Generative Adversarial Network
### Variational Autoencoders This is a visualization of a Generative Adversarial Network made using ManimML.
This is a visualization of a Variational Autoencoder. <img src="examples/media/GANScene.gif">
<img src="examples/media/VAEScene.gif">
### VAE Disentanglement ### VAE Disentanglement

190
examples/gan/gan.py Normal file
View File

@ -0,0 +1,190 @@
import random
from PIL import Image
from manim import *
from manim_ml.neural_network.layers.embedding import EmbeddingLayer
from manim_ml.neural_network.layers.feed_forward import FeedForwardLayer
from manim_ml.neural_network.layers.image import ImageLayer
from manim_ml.neural_network.layers.vector import VectorLayer
from manim_ml.neural_network.neural_network import NeuralNetwork
config.pixel_height = 1080
config.pixel_width = 1080
config.frame_height = 8.3
config.frame_width = 8.3
class GAN(Mobject):
"""Generative Adversarial Network"""
def __init__(self):
super().__init__()
self.make_entities()
self.place_entities()
self.titles = self.make_titles()
def make_entities(self, image_height=1.2):
"""Makes all of the network entities"""
# Make the fake image layer
default_image = Image.open('../../assets/gan/fake_image.png')
numpy_image = np.asarray(default_image)
self.fake_image_layer = ImageLayer(numpy_image, height=image_height, show_image_on_create=False)
# Make the Generator Network
self.generator = NeuralNetwork([
EmbeddingLayer(covariance=np.array([[3.0, 0], [0, 3.0]])).scale(1.3),
FeedForwardLayer(3),
FeedForwardLayer(5),
self.fake_image_layer
], layer_spacing=0.1)
self.add(self.generator)
# Make the Discriminator
self.discriminator = NeuralNetwork([
FeedForwardLayer(5),
FeedForwardLayer(1),
VectorLayer(1, value_func=lambda: random.uniform(0, 1)),
], layer_spacing=0.1)
self.add(self.discriminator)
# Make Ground Truth Dataset
default_image = Image.open('../../assets/gan/real_image.jpg')
numpy_image = np.asarray(default_image)
self.ground_truth_layer = ImageLayer(numpy_image, height=image_height)
self.add(self.ground_truth_layer)
self.scale(1)
def place_entities(self):
"""Positions entities in correct places"""
# Place relative to generator
# Place the ground_truth image layer
self.ground_truth_layer.next_to(self.fake_image_layer, DOWN, 0.8)
# Group the images
image_group = Group(self.ground_truth_layer, self.fake_image_layer)
# Move the discriminator to the right of thee generator
self.discriminator.next_to(self.generator, RIGHT, 0.2)
self.discriminator.match_y(image_group)
# Move the discriminator to the height of the center of the image_group
# self.discriminator.match_y(image_group)
# self.ground_truth_layer.next_to(self.fake_image_layer, DOWN, 0.5)
def make_titles(self):
"""Makes titles for the different entities"""
titles = VGroup()
self.ground_truth_layer_title = Text("Real Image").scale(0.3)
self.ground_truth_layer_title.next_to(self.ground_truth_layer, UP, 0.1)
self.add(self.ground_truth_layer_title)
titles.add(self.ground_truth_layer_title)
self.fake_image_layer_title = Text("Fake Image").scale(0.3)
self.fake_image_layer_title.next_to(self.fake_image_layer, UP, 0.1)
self.add(self.fake_image_layer_title)
titles.add(self.fake_image_layer_title)
# Overhead title
overhead_title = Text("Generative Adversarial Network").scale(0.75)
overhead_title.shift(np.array([0, 3.5, 0]))
titles.add(overhead_title)
# Probability title
self.probability_title = Text("Probability").scale(0.5)
self.probability_title.move_to(self.discriminator.input_layers[-2])
self.probability_title.shift(UP)
self.probability_title.shift(RIGHT*1.05)
titles.add(self.probability_title)
return titles
def make_highlight_generator_rectangle(self):
"""Returns animation that highlights the generators contents"""
group = VGroup()
generator_surrounding_group = Group(
self.generator,
self.fake_image_layer_title
)
generator_surrounding_rectangle = SurroundingRectangle(
generator_surrounding_group,
buff=0.1,
stroke_width=4.0,
color="#0FFF50"
)
group.add(generator_surrounding_rectangle)
title = Text("Generator").scale(0.5)
title.next_to(generator_surrounding_rectangle, UP, 0.2)
group.add(title)
return group
def make_highlight_discriminator_rectangle(self):
"""Makes a rectangle for highlighting the discriminator"""
discriminator_group = Group(
self.discriminator,
self.fake_image_layer,
self.ground_truth_layer,
self.fake_image_layer_title,
self.probability_title
)
group = VGroup()
discriminator_surrounding_rectangle = SurroundingRectangle(
discriminator_group,
buff=0.05,
stroke_width=4.0,
color="#0FFF50"
)
group.add(discriminator_surrounding_rectangle)
title = Text("Discriminator").scale(0.5)
title.next_to(discriminator_surrounding_rectangle, UP, 0.2)
group.add(title)
return group
def make_generator_forward_pass(self):
"""Makes forward pass of the generator"""
forward_pass = self.generator.make_forward_pass_animation(dist_theme="ellipse")
return forward_pass
def make_discriminator_forward_pass(self):
"""Makes forward pass of the discriminator"""
disc_forward = self.discriminator.make_forward_pass_animation()
return disc_forward
@override_animation(Create)
def _create_override(self):
"""Overrides create"""
animation_group = AnimationGroup(
Create(self.generator),
Create(self.discriminator),
Create(self.ground_truth_layer),
Create(self.titles)
)
return animation_group
class GANScene(Scene):
"""GAN Scene"""
def construct(self):
gan = GAN().scale(1.70)
gan.move_to(ORIGIN)
gan.shift(DOWN*0.35)
gan.shift(LEFT*0.1)
self.play(Create(gan), run_time=3)
# Highlight generator
highlight_generator_rectangle = gan.make_highlight_generator_rectangle()
self.play(Create(highlight_generator_rectangle), run_time=1)
# Generator forward pass
gen_forward_pass = gan.make_generator_forward_pass()
self.play(gen_forward_pass, run_time=5)
# Fade out generator highlight
self.play(Uncreate(highlight_generator_rectangle), run_time=1)
# Highlight discriminator
highlight_discriminator_rectangle = gan.make_highlight_discriminator_rectangle()
self.play(Create(highlight_discriminator_rectangle), run_time=1)
# Discriminator forward pass
discriminator_forward_pass = gan.make_discriminator_forward_pass()
self.play(discriminator_forward_pass, run_time=5)
# Unhighlight discriminator
self.play(Uncreate(highlight_discriminator_rectangle), run_time=1)

BIN
examples/media/GANScene.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 991 KiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

View File

@ -8,7 +8,12 @@ from manim import *
import pickle import pickle
import numpy as np import numpy as np
import os import os
from PIL import Image
import manim_ml.neural_network as neural_network import manim_ml.neural_network as neural_network
from manim_ml.neural_network.embedding import EmbeddingLayer
from manim_ml.neural_network.feed_forward import FeedForwardLayer
from manim_ml.neural_network.image import ImageLayer
from manim_ml.neural_network.neural_network import NeuralNetwork
class VariationalAutoencoder(VGroup): class VariationalAutoencoder(VGroup):
"""Variational Autoencoder Manim Visualization""" """Variational Autoencoder Manim Visualization"""
@ -239,6 +244,29 @@ class VariationalAutoencoder(VGroup):
return animation_group return animation_group
class VariationalAutoencoder(VGroup):
def __init__(self):
embedding_layer = EmbeddingLayer()
image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image)
# Make nn
neural_network = NeuralNetwork([
ImageLayer(numpy_image, height=1.4),
FeedForwardLayer(5),
FeedForwardLayer(3),
embedding_layer,
FeedForwardLayer(3),
FeedForwardLayer(5),
ImageLayer(numpy_image, height=1.4),
])
neural_network.scale(1.3)
self.play(Create(neural_network))
self.play(neural_network.make_forward_pass_animation(run_time=15))
class MNISTImageHandler(): class MNISTImageHandler():
"""Deals with loading serialized VAE mnist images from "autoencoder_models" """ """Deals with loading serialized VAE mnist images from "autoencoder_models" """
@ -295,19 +323,4 @@ class VAEScene(Scene):
# Interpolation animation # Interpolation animation
interpolation_images = mnist_image_handler.interpolation_images interpolation_images = mnist_image_handler.interpolation_images
interpolation_animation = vae.make_interpolation_animation(interpolation_images) interpolation_animation = vae.make_interpolation_animation(interpolation_images)
self.play(interpolation_animation) self.play(interpolation_animation)
class VAEImage(Scene):
def construct(self):
# Set Scene config
vae = VariationalAutoencoder()
mnist_image_handler = MNISTImageHandler()
image_pair = mnist_image_handler.image_pairs[3]
vae.move_to(ORIGIN)
vae.scale(1.3)
self.play(Create(vae), run_time=3)
# Make a forward pass animation
forward_pass_animation = vae.make_forward_pass_animation(image_pair)
self.play(forward_pass_animation)

View File

@ -5,18 +5,17 @@ from manim_ml.neural_network.layers.parent_layers import VGroupNeuralNetworkLaye
class EmbeddingLayer(VGroupNeuralNetworkLayer): class EmbeddingLayer(VGroupNeuralNetworkLayer):
"""NeuralNetwork embedding object that can show probability distributions""" """NeuralNetwork embedding object that can show probability distributions"""
def __init__(self, point_radius=0.02, **kwargs): def __init__(self, point_radius=0.02, mean = np.array([0, 0]),
covariance=np.array([[1.5, 0], [0, 1.5]]), **kwargs):
super(VGroupNeuralNetworkLayer, self).__init__(**kwargs) super(VGroupNeuralNetworkLayer, self).__init__(**kwargs)
self.point_radius = point_radius self.point_radius = point_radius
self.axes = Axes( self.axes = Axes(
tips=False, tips=False,
x_length=1, x_length=0.8,
y_length=1 y_length=0.8
) )
self.add(self.axes) self.add(self.axes)
# Make point cloud # Make point cloud
mean = np.array([0, 0])
covariance = np.array([[1.5, 0], [0, 1.5]])
self.point_cloud = self.construct_gaussian_point_cloud(mean, covariance) self.point_cloud = self.construct_gaussian_point_cloud(mean, covariance)
self.add(self.point_cloud) self.add(self.point_cloud)
# Make latent distribution # Make latent distribution
@ -50,10 +49,14 @@ class EmbeddingLayer(VGroupNeuralNetworkLayer):
return point_dots return point_dots
def make_forward_pass_animation(self): def make_forward_pass_animation(self, dist_theme="gaussian", **kwargs):
"""Forward pass animation""" """Forward pass animation"""
# Make ellipse object corresponding to the latent distribution # Make ellipse object corresponding to the latent distribution
self.latent_distribution = GaussianDistribution(self.axes) # Use defaults self.latent_distribution = GaussianDistribution(
self.axes,
dist_theme=dist_theme,
cov=np.array([[0.8, 0], [0.0, 0.8]])
) # Use defaults
# Create animation # Create animation
animations = [] animations = []
#create_distribution = Create(self.latent_distribution.construct_gaussian_distribution(self.latent_distribution.mean, self.latent_distribution.cov)) #Create(self.latent_distribution) #create_distribution = Create(self.latent_distribution.construct_gaussian_distribution(self.latent_distribution.mean, self.latent_distribution.cov)) #Create(self.latent_distribution)

View File

@ -17,7 +17,7 @@ class EmbeddingToFeedForward(ConnectiveLayer):
self.animation_dot_color = animation_dot_color self.animation_dot_color = animation_dot_color
self.dot_radius = dot_radius self.dot_radius = dot_radius
def make_forward_pass_animation(self, run_time=1.5): def make_forward_pass_animation(self, run_time=1.5, **kwargs):
"""Makes dots diverge from the given location and move the decoder""" """Makes dots diverge from the given location and move the decoder"""
# Find point to converge on by sampling from gaussian distribution # Find point to converge on by sampling from gaussian distribution
location = self.embedding_layer.sample_point_location_from_distribution() location = self.embedding_layer.sample_point_location_from_distribution()

View File

@ -44,7 +44,7 @@ class FeedForwardLayer(VGroupNeuralNetworkLayer):
# Add the objects to the class # Add the objects to the class
self.add(self.surrounding_rectangle, self.node_group) self.add(self.surrounding_rectangle, self.node_group)
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
# make highlight animation # make highlight animation
succession = Succession( succession = Succession(
ApplyMethod(self.node_group.set_color, self.animation_dot_color, run_time=0.25), ApplyMethod(self.node_group.set_color, self.animation_dot_color, run_time=0.25),

View File

@ -33,7 +33,18 @@ class FeedForwardToFeedForward(ConnectiveLayer):
edges = VGroup(*edges) edges = VGroup(*edges)
return edges return edges
def make_forward_pass_animation(self, run_time=1): @override_animation(FadeOut)
def _fadeout_animation(self):
animations = []
for edge in self.edges:
animations.append(FadeOut(edge))
animation_group = AnimationGroup(*animations)
return animation_group
def make_forward_pass_animation(self, run_time=1, **kwargs):
"""Animation for passing information from one FeedForwardLayer to the next""" """Animation for passing information from one FeedForwardLayer to the next"""
path_animations = [] path_animations = []
dots = [] dots = []

View File

@ -18,7 +18,7 @@ class FeedForwardToImage(ConnectiveLayer):
self.feed_forward_layer = input_layer self.feed_forward_layer = input_layer
self.image_layer = output_layer self.image_layer = output_layer
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Makes dots diverge from the given location and move to the feed forward nodes decoder""" """Makes dots diverge from the given location and move to the feed forward nodes decoder"""
animations = [] animations = []
image_mobject = self.image_layer.image_mobject image_mobject = self.image_layer.image_mobject

View File

@ -5,9 +5,10 @@ from manim_ml.neural_network.layers.parent_layers import NeuralNetworkLayer
class ImageLayer(NeuralNetworkLayer): class ImageLayer(NeuralNetworkLayer):
"""Single Image Layer for Neural Network""" """Single Image Layer for Neural Network"""
def __init__(self, numpy_image, height=1.5, **kwargs): def __init__(self, numpy_image, height=1.5, show_image_on_create=True, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.numpy_image = numpy_image self.numpy_image = numpy_image
self.show_image_on_create = show_image_on_create
if len(np.shape(self.numpy_image)) == 2: if len(np.shape(self.numpy_image)) == 2:
# Assumed Grayscale # Assumed Grayscale
self.image_mobject = GrayscaleImageMobject(self.numpy_image, height=height) self.image_mobject = GrayscaleImageMobject(self.numpy_image, height=height)
@ -21,9 +22,12 @@ class ImageLayer(NeuralNetworkLayer):
debug_mode = False debug_mode = False
if debug_mode: if debug_mode:
return FadeIn(SurroundingRectangle(self.image_mobject)) return FadeIn(SurroundingRectangle(self.image_mobject))
return FadeIn(self.image_mobject) if self.show_image_on_create:
return FadeIn(self.image_mobject)
else:
return AnimationGroup()
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
return FadeIn(self.image_mobject) return FadeIn(self.image_mobject)
# def move_to(self, location): # def move_to(self, location):
@ -36,4 +40,8 @@ class ImageLayer(NeuralNetworkLayer):
@property @property
def width(self): def width(self):
return self.image_mobject.width return self.image_mobject.width
@property
def height(self):
return self.image_mobject.height

View File

@ -18,7 +18,7 @@ class ImageToFeedForward(ConnectiveLayer):
self.feed_forward_layer = output_layer self.feed_forward_layer = output_layer
self.image_layer = input_layer self.image_layer = input_layer
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Makes dots diverge from the given location and move to the feed forward nodes decoder""" """Makes dots diverge from the given location and move to the feed forward nodes decoder"""
animations = [] animations = []
dots = [] dots = []

View File

@ -60,6 +60,6 @@ class PairedQueryLayer(NeuralNetworkLayer):
# TODO make Create animation that is custom # TODO make Create animation that is custom
return FadeIn(self.assets) return FadeIn(self.assets)
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Forward pass for query""" """Forward pass for query"""
return AnimationGroup() return AnimationGroup()

View File

@ -17,7 +17,7 @@ class PairedQueryToFeedForward(ConnectiveLayer):
self.paired_query_layer = input_layer self.paired_query_layer = input_layer
self.feed_forward_layer = output_layer self.feed_forward_layer = output_layer
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Makes dots diverge from the given location and move to the feed forward nodes decoder""" """Makes dots diverge from the given location and move to the feed forward nodes decoder"""
animations = [] animations = []
# Loop through each image # Loop through each image

View File

@ -7,10 +7,12 @@ class NeuralNetworkLayer(ABC, Group):
def __init__(self, text=None, **kwargs): def __init__(self, text=None, **kwargs):
super(Group, self).__init__() super(Group, self).__init__()
self.title_text = kwargs["title"] if "title" in kwargs else " " self.title_text = kwargs["title"] if "title" in kwargs else " "
self.title = Text(self.title_text, font_size=DEFAULT_FONT_SIZE/3) self.title = Text(self.title_text, font_size=DEFAULT_FONT_SIZE/3).scale(0.6)
# self.title.next_to(self, UP, 1.2)
# self.add(self.title)
@abstractmethod @abstractmethod
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
pass pass
@override_animation(Create) @override_animation(Create)
@ -26,7 +28,7 @@ class VGroupNeuralNetworkLayer(NeuralNetworkLayer):
super().__init__(**kwargs) super().__init__(**kwargs)
@abstractmethod @abstractmethod
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
pass pass
@override_animation(Create) @override_animation(Create)
@ -49,7 +51,7 @@ class ConnectiveLayer(VGroupNeuralNetworkLayer):
assert isinstance(output_layer, self.output_class) assert isinstance(output_layer, self.output_class)
@abstractmethod @abstractmethod
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
pass pass
@override_animation(Create) @override_animation(Create)

View File

@ -71,6 +71,6 @@ class TripletLayer(NeuralNetworkLayer):
# TODO make Create animation that is custom # TODO make Create animation that is custom
return FadeIn(self.assets) return FadeIn(self.assets)
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Forward pass for triplet""" """Forward pass for triplet"""
return AnimationGroup() return AnimationGroup()

View File

@ -18,7 +18,7 @@ class TripletToFeedForward(ConnectiveLayer):
self.feed_forward_layer = output_layer self.feed_forward_layer = output_layer
self.triplet_layer = input_layer self.triplet_layer = input_layer
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
"""Makes dots diverge from the given location and move to the feed forward nodes decoder""" """Makes dots diverge from the given location and move to the feed forward nodes decoder"""
animations = [] animations = []
# Loop through each image # Loop through each image

View File

@ -23,12 +23,12 @@ class VectorLayer(VGroupNeuralNetworkLayer):
values = values[None, :].T values = values[None, :].T
vector = Matrix(values) vector = Matrix(values)
vector_label = Text(f"[{self.value_func():.2}]") vector_label = Text(f"[{self.value_func():.2f}]")
vector_label.scale(0.5) vector_label.scale(0.3)
return vector_label return vector_label
def make_forward_pass_animation(self): def make_forward_pass_animation(self, **kwargs):
return AnimationGroup() return AnimationGroup()
@override_animation(Create) @override_animation(Create)

View File

@ -9,35 +9,15 @@ Example:
# Create the object with default style settings # Create the object with default style settings
NeuralNetwork(layer_node_count) NeuralNetwork(layer_node_count)
""" """
from socket import create_connection
from manim import * from manim import *
import warnings import warnings
import textwrap import textwrap
from manim_ml.neural_network.layers import connective_layers_list
from manim_ml.neural_network.layers.feed_forward import FeedForwardLayer from manim_ml.neural_network.layers.feed_forward import FeedForwardLayer
from manim_ml.neural_network.layers.util import get_connective_layer from manim_ml.neural_network.layers.util import get_connective_layer
from manim_ml.list_group import ListGroup from manim_ml.list_group import ListGroup
class LazyAnimation(Animation): class RemoveLayer(AnimationGroup):
"""
Lazily creates animation when the animation is called.
This is helpful when creating the animation depends upon the internal
state of some set of objects.
"""
def __init__(self, animation_func):
self.animation_func = animation_func
super().__init__(None)
def begin(self):
"""Begins animation"""
self.mobject, animation = self.animation_func()
animation = Create(self.mobject)
animation.begin()
class RemoveLayer(Succession):
""" """
Animation for removing a layer from a neural network. Animation for removing a layer from a neural network.
@ -183,6 +163,99 @@ class RemoveLayer(Succession):
update_func_anim = UpdateFromFunc(self.neural_network, create_new_connective) update_func_anim = UpdateFromFunc(self.neural_network, create_new_connective)
return update_func_anim return update_func_anim
class InsertLayer(AnimationGroup):
"""Animation for inserting layer at given index"""
def __init__(self, layer, index, neural_network):
self.layer = layer
self.index = index
self.neural_network = neural_network
# Layers before and after
self.layers_before = self.neural_network.all_layers[:self.index]
self.layers_after = self.neural_network.all_layers[self.index:]
remove_connective_layer = self.remove_connective_layer()
move_layers = self.make_move_layers()
# create_layer = self.make_create_layer()
# create_connective_layers = self.make_create_connective_layers()
animations = [
remove_connective_layer,
move_layers,
# create_layer,
# create_connective_layers
]
super().__init__(*animations, lag_ratio=1.0)
def remove_connective_layer(self):
"""Removes the connective layer before the insertion index"""
# Check if connective layer exists
if len(self.layers_before) > 0:
removed_connective = self.layers_before[-1]
self.neural_network.all_layers.remove(removed_connective)
# Make remove animation
remove_animation = FadeOut(removed_connective)
return remove_animation
return AnimationGroup()
def make_move_layers(self):
"""Shifts layers before and after"""
# Before layer shift
before_shift_animation = AnimationGroup()
if len(self.layers_before) > 0:
before_shift = np.array([-self.layer.width/2, 0, 0])
# Shift layers before
before_shift_animation = Group(*self.layers_before).animate.shift(before_shift)
# After layer shift
after_shift_animation = AnimationGroup()
if len(self.layers_after) > 0:
after_shift = np.array([self.layer.width/2, 0, 0])
# Shift layers after
after_shift_animation = Group(*self.layers_after).animate.shift(after_shift)
# Make animation group
shift_animations = AnimationGroup(
before_shift_animation,
after_shift_animation
)
return shift_animations
def make_create_layer(self):
"""Animates the creation of the layer"""
pass
def make_create_connective_layers(self):
pass
# Make connective layers and shift animations
# Before layer
if len(layers_before) > 0:
before_connective = get_connective_layer(layers_before[-1], layer)
before_shift = np.array([-layer.width/2, 0, 0])
# Shift layers before
before_shift_animation = Group(*layers_before).animate.shift(before_shift)
else:
before_connective = AnimationGroup()
# After layer
if len(layers_after) > 0:
after_connective = get_connective_layer(layer, layers_after[0])
after_shift = np.array([layer.width/2, 0, 0])
# Shift layers after
after_shift_animation = Group(*layers_after).animate.shift(after_shift)
else:
after_connective = AnimationGroup
insert_animation = Create(layer)
animation_group = AnimationGroup(
shift_animations,
insert_animation,
lag_ratio=1.0
)
return animation_group
class NeuralNetwork(Group): class NeuralNetwork(Group):
@ -202,9 +275,6 @@ class NeuralNetwork(Group):
# and make it have explicit distinct subspaces # and make it have explicit distinct subspaces
self._place_layers() self._place_layers()
self.connective_layers, self.all_layers = self._construct_connective_layers() self.connective_layers, self.all_layers = self._construct_connective_layers()
# Make layer titles
self.layer_titles = self._make_layer_titles()
self.add(self.layer_titles)
# Make overhead title # Make overhead title
self.title = Text(self.title_text, font_size=DEFAULT_FONT_SIZE/2) self.title = Text(self.title_text, font_size=DEFAULT_FONT_SIZE/2)
self.title.next_to(self, UP, 1.0) self.title.next_to(self, UP, 1.0)
@ -229,15 +299,6 @@ class NeuralNetwork(Group):
shift_vector = np.array([(previous_layer.get_width()/2 + current_layer.get_width()/2) + self.layer_spacing, 0, 0]) shift_vector = np.array([(previous_layer.get_width()/2 + current_layer.get_width()/2) + self.layer_spacing, 0, 0])
current_layer.shift(shift_vector) current_layer.shift(shift_vector)
def _make_layer_titles(self):
"""Makes titles"""
titles = VGroup()
for layer in self.all_layers:
title = layer.title
title.next_to(layer, UP, 0.2)
titles.add(title)
return titles
def _construct_connective_layers(self): def _construct_connective_layers(self):
"""Draws connecting lines between layers""" """Draws connecting lines between layers"""
connective_layers = ListGroup() connective_layers = ListGroup()
@ -264,40 +325,9 @@ class NeuralNetwork(Group):
def insert_layer(self, layer, insert_index): def insert_layer(self, layer, insert_index):
"""Inserts a layer at the given index""" """Inserts a layer at the given index"""
layers_before = self.all_layers[:insert_index] neural_network = self
layers_after = self.all_layers[insert_index:] insert_animation = InsertLayer(layer, insert_index, neural_network)
# Make connective layers and shift animations return insert_animation
# Before layer
if len(layers_before) > 0:
before_connective = get_connective_layer(layers_before[-1], layer)
before_shift = np.array([-layer.width/2, 0, 0])
# Shift layers before
before_shift_animation = Group(*layers_before).animate.shift(before_shift)
else:
before_connective = AnimationGroup()
# After layer
if len(layers_after) > 0:
after_connective = get_connective_layer(layer, layers_after[0])
after_shift = np.array([layer.width/2, 0, 0])
# Shift layers after
after_shift_animation = Group(*layers_after).animate.shift(after_shift)
else:
after_connective = AnimationGroup
# Make animation group
shift_animations = AnimationGroup(
before_shift_animation,
after_shift_animation
)
insert_animation = Create(layer)
animation_group = AnimationGroup(
shift_animations,
insert_animation,
lag_ratio=1.0
)
return animation_group
def remove_layer(self, layer): def remove_layer(self, layer):
"""Removes layer object if it exists""" """Removes layer object if it exists"""
@ -317,17 +347,18 @@ class NeuralNetwork(Group):
return animation_group return animation_group
def make_forward_pass_animation(self, run_time=10, passing_flash=True): def make_forward_pass_animation(self, run_time=10, passing_flash=True,
**kwargs):
"""Generates an animation for feed forward propagation""" """Generates an animation for feed forward propagation"""
all_animations = [] all_animations = []
for layer_index, layer in enumerate(self.input_layers[:-1]): for layer_index, layer in enumerate(self.input_layers[:-1]):
layer_forward_pass = layer.make_forward_pass_animation() layer_forward_pass = layer.make_forward_pass_animation(**kwargs)
all_animations.append(layer_forward_pass) all_animations.append(layer_forward_pass)
connective_layer = self.connective_layers[layer_index] connective_layer = self.connective_layers[layer_index]
connective_forward_pass = connective_layer.make_forward_pass_animation() connective_forward_pass = connective_layer.make_forward_pass_animation(**kwargs)
all_animations.append(connective_forward_pass) all_animations.append(connective_forward_pass)
# Do last layer animation # Do last layer animation
last_layer_forward_pass = self.input_layers[-1].make_forward_pass_animation() last_layer_forward_pass = self.input_layers[-1].make_forward_pass_animation(**kwargs)
all_animations.append(last_layer_forward_pass) all_animations.append(last_layer_forward_pass)
# Make the animation group # Make the animation group
animation_group = AnimationGroup(*all_animations, run_time=run_time, lag_ratio=1.0) animation_group = AnimationGroup(*all_animations, run_time=run_time, lag_ratio=1.0)

View File

@ -5,17 +5,23 @@ import math
class GaussianDistribution(VGroup): class GaussianDistribution(VGroup):
"""Object for drawing a Gaussian distribution""" """Object for drawing a Gaussian distribution"""
def __init__(self, axes, mean=None, cov=None, **kwargs): def __init__(self, axes, mean=None, cov=None, dist_theme="gaussian", **kwargs):
super(VGroup, self).__init__(**kwargs) super(VGroup, self).__init__(**kwargs)
self.axes = axes self.axes = axes
self.mean = mean self.mean = mean
self.cov = cov self.cov = cov
self.dist_theme = dist_theme
if mean is None: if mean is None:
self.mean = np.array([0.0, 0.0]) self.mean = np.array([0.0, 0.0])
if cov is None: if cov is None:
self.cov = np.array([[3, 0], [0, 3]]) self.cov = np.array([[1, 0], [0, 1]])
# Make the Gaussian # Make the Gaussian
self.ellipses = self.construct_gaussian_distribution(self.mean, self.cov) if self.dist_theme is "gaussian":
self.ellipses = self.construct_gaussian_distribution(self.mean, self.cov)
elif self.dist_theme is "ellipse":
self.ellipses = self.construct_simple_gaussian_ellipse(self.mean, self.cov)
else:
raise Exception(f"Uncrecognized distribution theme: {self.dist_theme}")
@override_animation(Create) @override_animation(Create)
def _create_gaussian_distribution(self): def _create_gaussian_distribution(self):
@ -65,3 +71,30 @@ class GaussianDistribution(VGroup):
return ellipses return ellipses
def construct_simple_gaussian_ellipse(self, mean, covariance, color=ORANGE):
"""Returns a 2d Gaussian distribution object with given mean and covariance"""
# map mean and covariance to frame coordinates
mean = self.axes.coords_to_point(*mean)
# Figure out the scale and angle of rotation
# TODO fix this
# rotation, width, height = self.compute_covariance_rotation_and_scale(covariance)
mean = np.array([0, 0, 0])
mean = self.axes.coords_to_point(*mean)
rotation = 0.0
# Make covariance ellipses
opacity = 0.0
ellipses = VGroup()
opacity = 0.2
ellipse = Ellipse(
width=0.6,
height=0.6,
color=color,
fill_opacity=opacity,
stroke_width=2.0
)
ellipse.move_to(mean)
ellipse.rotate(rotation)
ellipses.add(ellipse)
return ellipses

View File

@ -0,0 +1,42 @@
from manim import *
from manim_ml.neural_network.layers.convolutional import ConvolutionalLayer
from manim_ml.neural_network.layers.feed_forward import FeedForwardLayer
from manim_ml.neural_network.neural_network import NeuralNetwork
class SingleConvolutionalLayerScence(Scene):
def construct(self):
# Make nn
layers = [
ConvolutionalLayer()
]
nn = NeuralNetwork(layers)
nn.scale(1.3)
# Center the nn
nn.move_to(ORIGIN)
self.add(nn)
# Play animation
self.play(nn.make_forward_pass_animation(run_time=5))
self.play(nn.make_forward_pass_animation(run_time=5))
class ThreeDLightSourcePosition(ThreeDScene, Scene):
def construct(self):
axes = ThreeDAxes()
sphere = Surface(
lambda u, v: np.array([
u,
v,
0
]), v_range=[0, TAU], u_range=[-PI / 2, PI / 2],
checkerboard_colors=[RED_D, RED_E], resolution=(15, 32)
)
self.renderer.camera.light_source.move_to(3*IN) # changes the source of the light
self.set_camera_orientation(phi=90 * DEGREES, theta=0 * DEGREES)
self.add(axes, sphere)
class CombinedScene(Scene):
def constuct(self):
pass

View File

@ -107,18 +107,18 @@ class NeuralNetworkScene(Scene):
self.play(forward_propagation_animation) self.play(forward_propagation_animation)
class ImageNeuralNetworkScene(Scene): class GrayscaleImageNeuralNetworkScene(Scene):
def construct(self): def construct(self):
image = Image.open('images/image.jpeg') image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image) numpy_image = np.asarray(image)
# Make nn # Make nn
layers = [ layers = [
ImageLayer(numpy_image, height=1.4), FeedForwardLayer(3),
FeedForwardLayer(3),
FeedForwardLayer(5), FeedForwardLayer(5),
FeedForwardLayer(3), FeedForwardLayer(3),
FeedForwardLayer(6) FeedForwardLayer(6),
ImageLayer(numpy_image, height=1.4)
] ]
nn = NeuralNetwork(layers) nn = NeuralNetwork(layers)
nn.scale(1.3) nn.scale(1.3)
@ -129,6 +129,27 @@ class ImageNeuralNetworkScene(Scene):
self.play(nn.make_forward_pass_animation(run_time=5)) self.play(nn.make_forward_pass_animation(run_time=5))
self.play(nn.make_forward_pass_animation(run_time=5)) self.play(nn.make_forward_pass_animation(run_time=5))
class ImageNeuralNetworkScene(Scene):
def construct(self):
image = Image.open('../assets/gan/real_image.jpg')
numpy_image = np.asarray(image)
# Make nn
layers = [
FeedForwardLayer(3),
FeedForwardLayer(5),
FeedForwardLayer(3),
FeedForwardLayer(6),
ImageLayer(numpy_image, height=1.4)
]
nn = NeuralNetwork(layers)
nn.scale(1.3)
# Center the nn
nn.move_to(ORIGIN)
self.add(nn)
# Play animation
self.play(nn.make_forward_pass_animation(run_time=5))
self.play(nn.make_forward_pass_animation(run_time=5))
class EmbeddingNNScene(Scene): class EmbeddingNNScene(Scene):
@ -174,7 +195,7 @@ class LayerRemovalScene(Scene):
image = Image.open('images/image.jpeg') image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image) numpy_image = np.asarray(image)
layer = FeedForwardLayer(5), layer = FeedForwardLayer(5)
layers = [ layers = [
ImageLayer(numpy_image, height=1.4), ImageLayer(numpy_image, height=1.4),
FeedForwardLayer(3), FeedForwardLayer(3),
@ -186,7 +207,34 @@ class LayerRemovalScene(Scene):
nn = NeuralNetwork(layers) nn = NeuralNetwork(layers)
self.play(Create(nn)) self.play(Create(nn))
self.play(nn.remove_layer(layer)) remove_animation = nn.remove_layer(layer)
print("before remove")
self.play(remove_animation)
print(nn)
print("after remove")
class LayerInsertionScene(Scene):
def construct(self):
image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image)
layers = [
ImageLayer(numpy_image, height=1.4),
FeedForwardLayer(3),
FeedForwardLayer(3),
FeedForwardLayer(6)
]
nn = NeuralNetwork(layers)
self.play(Create(nn))
layer = FeedForwardLayer(5)
insert_animation = nn.insert_layer(layer, 4)
self.play(insert_animation)
print(nn)
print("after remove")
if __name__ == "__main__": if __name__ == "__main__":
"""Render all scenes""" """Render all scenes"""

View File

@ -1,19 +1,17 @@
from manim import * from manim import *
from PIL import Image from PIL import Image
from manim_ml.neural_network.embedding import EmbeddingLayer from manim_ml.neural_network.layers import EmbeddingLayer, FeedForwardLayer, ImageLayer
from manim_ml.neural_network.feed_forward import FeedForwardLayer
from manim_ml.neural_network.image import ImageLayer
from manim_ml.neural_network.neural_network import NeuralNetwork from manim_ml.neural_network.neural_network import NeuralNetwork
config.pixel_height = 720 config.pixel_height = 720
config.pixel_width = 1280 config.pixel_width = 1280
config.frame_height = 6.0 config.frame_height = 8.0
config.frame_width = 6.0 config.frame_width = 8.0
class VariationalAutoencoderScene(Scene): class VariationalAutoencoderScene(Scene):
def construct(self): def construct(self):
embedding_layer = EmbeddingLayer() embedding_layer = EmbeddingLayer(dist_theme="ellipse").scale(2)
image = Image.open('images/image.jpeg') image = Image.open('images/image.jpeg')
numpy_image = np.asarray(image) numpy_image = np.asarray(image)
@ -26,7 +24,7 @@ class VariationalAutoencoderScene(Scene):
FeedForwardLayer(3), FeedForwardLayer(3),
FeedForwardLayer(5), FeedForwardLayer(5),
ImageLayer(numpy_image, height=1.4), ImageLayer(numpy_image, height=1.4),
]) ], layer_spacing=0.1)
neural_network.scale(1.3) neural_network.scale(1.3)