mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
4586 lines
137 KiB
Python
4586 lines
137 KiB
Python
import sys
|
|
import os.path
|
|
import cv2
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
from big_ol_pile_of_manim_imports import *
|
|
|
|
from nn.network import *
|
|
|
|
#force_skipping
|
|
#revert_to_original_skipping_status
|
|
|
|
|
|
DEFAULT_GAUSS_BLUR_CONFIG = {
|
|
"ksize" : (5, 5),
|
|
"sigmaX" : 10,
|
|
"sigmaY" : 10,
|
|
}
|
|
|
|
DEFAULT_CANNY_CONFIG = {
|
|
"threshold1" : 100,
|
|
"threshold2" : 200,
|
|
}
|
|
|
|
|
|
def get_edges(image_array):
|
|
blurred = cv2.GaussianBlur(
|
|
image_array,
|
|
**DEFAULT_GAUSS_BLUR_CONFIG
|
|
)
|
|
edges = cv2.Canny(
|
|
blurred,
|
|
**DEFAULT_CANNY_CONFIG
|
|
)
|
|
return edges
|
|
|
|
class WrappedImage(Group):
|
|
CONFIG = {
|
|
"rect_kwargs" : {
|
|
"color" : BLUE,
|
|
"buff" : SMALL_BUFF,
|
|
}
|
|
}
|
|
def __init__(self, image_mobject, **kwargs):
|
|
Group.__init__(self, **kwargs)
|
|
rect = SurroundingRectangle(
|
|
image_mobject, **self.rect_kwargs
|
|
)
|
|
self.add(rect, image_mobject)
|
|
|
|
class PixelsAsSquares(VGroup):
|
|
CONFIG = {
|
|
"height" : 2,
|
|
}
|
|
def __init__(self, image_mobject, **kwargs):
|
|
VGroup.__init__(self, **kwargs)
|
|
for row in image_mobject.pixel_array:
|
|
for rgba in row:
|
|
square = Square(
|
|
stroke_width = 0,
|
|
fill_opacity = rgba[3]/255.0,
|
|
fill_color = rgba_to_color(rgba/255.0),
|
|
)
|
|
self.add(square)
|
|
self.arrange_in_grid(
|
|
*image_mobject.pixel_array.shape[:2],
|
|
buff = 0
|
|
)
|
|
self.replace(image_mobject)
|
|
|
|
class PixelsFromVect(PixelsAsSquares):
|
|
def __init__(self, vect, **kwargs):
|
|
PixelsAsSquares.__init__(self,
|
|
ImageMobject(layer_to_image_array(vect)),
|
|
**kwargs
|
|
)
|
|
|
|
class MNistMobject(WrappedImage):
|
|
def __init__(self, vect, **kwargs):
|
|
WrappedImage.__init__(self,
|
|
ImageMobject(layer_to_image_array(vect)),
|
|
**kwargs
|
|
)
|
|
|
|
class NetworkMobject(VGroup):
|
|
CONFIG = {
|
|
"neuron_radius" : 0.15,
|
|
"neuron_to_neuron_buff" : MED_SMALL_BUFF,
|
|
"layer_to_layer_buff" : LARGE_BUFF,
|
|
"neuron_stroke_color" : BLUE,
|
|
"neuron_stroke_width" : 3,
|
|
"neuron_fill_color" : GREEN,
|
|
"edge_color" : LIGHT_GREY,
|
|
"edge_stroke_width" : 2,
|
|
"edge_propogation_color" : YELLOW,
|
|
"edge_propogation_time" : 1,
|
|
"max_shown_neurons" : 16,
|
|
"brace_for_large_layers" : True,
|
|
"average_shown_activation_of_large_layer" : True,
|
|
"include_output_labels" : False,
|
|
}
|
|
def __init__(self, neural_network, **kwargs):
|
|
VGroup.__init__(self, **kwargs)
|
|
self.neural_network = neural_network
|
|
self.layer_sizes = neural_network.sizes
|
|
self.add_neurons()
|
|
self.add_edges()
|
|
|
|
def add_neurons(self):
|
|
layers = VGroup(*[
|
|
self.get_layer(size)
|
|
for size in self.layer_sizes
|
|
])
|
|
layers.arrange(RIGHT, buff = self.layer_to_layer_buff)
|
|
self.layers = layers
|
|
self.add(self.layers)
|
|
if self.include_output_labels:
|
|
self.add_output_labels()
|
|
|
|
def get_layer(self, size):
|
|
layer = VGroup()
|
|
n_neurons = size
|
|
if n_neurons > self.max_shown_neurons:
|
|
n_neurons = self.max_shown_neurons
|
|
neurons = VGroup(*[
|
|
Circle(
|
|
radius = self.neuron_radius,
|
|
stroke_color = self.neuron_stroke_color,
|
|
stroke_width = self.neuron_stroke_width,
|
|
fill_color = self.neuron_fill_color,
|
|
fill_opacity = 0,
|
|
)
|
|
for x in range(n_neurons)
|
|
])
|
|
neurons.arrange(
|
|
DOWN, buff = self.neuron_to_neuron_buff
|
|
)
|
|
for neuron in neurons:
|
|
neuron.edges_in = VGroup()
|
|
neuron.edges_out = VGroup()
|
|
layer.neurons = neurons
|
|
layer.add(neurons)
|
|
|
|
if size > n_neurons:
|
|
dots = TexMobject("\\vdots")
|
|
dots.move_to(neurons)
|
|
VGroup(*neurons[:len(neurons) // 2]).next_to(
|
|
dots, UP, MED_SMALL_BUFF
|
|
)
|
|
VGroup(*neurons[len(neurons) // 2:]).next_to(
|
|
dots, DOWN, MED_SMALL_BUFF
|
|
)
|
|
layer.dots = dots
|
|
layer.add(dots)
|
|
if self.brace_for_large_layers:
|
|
brace = Brace(layer, LEFT)
|
|
brace_label = brace.get_tex(str(size))
|
|
layer.brace = brace
|
|
layer.brace_label = brace_label
|
|
layer.add(brace, brace_label)
|
|
|
|
return layer
|
|
|
|
def add_edges(self):
|
|
self.edge_groups = VGroup()
|
|
for l1, l2 in zip(self.layers[:-1], self.layers[1:]):
|
|
edge_group = VGroup()
|
|
for n1, n2 in it.product(l1.neurons, l2.neurons):
|
|
edge = self.get_edge(n1, n2)
|
|
edge_group.add(edge)
|
|
n1.edges_out.add(edge)
|
|
n2.edges_in.add(edge)
|
|
self.edge_groups.add(edge_group)
|
|
self.add_to_back(self.edge_groups)
|
|
|
|
def get_edge(self, neuron1, neuron2):
|
|
return Line(
|
|
neuron1.get_center(),
|
|
neuron2.get_center(),
|
|
buff = self.neuron_radius,
|
|
stroke_color = self.edge_color,
|
|
stroke_width = self.edge_stroke_width,
|
|
)
|
|
|
|
def get_active_layer(self, layer_index, activation_vector):
|
|
layer = self.layers[layer_index].deepcopy()
|
|
self.activate_layer(layer, activation_vector)
|
|
return layer
|
|
|
|
def activate_layer(self, layer, activation_vector):
|
|
n_neurons = len(layer.neurons)
|
|
av = activation_vector
|
|
def arr_to_num(arr):
|
|
return (np.sum(arr > 0.1) / float(len(arr)))**(1./3)
|
|
|
|
if len(av) > n_neurons:
|
|
if self.average_shown_activation_of_large_layer:
|
|
indices = np.arange(n_neurons)
|
|
indices *= int(len(av)/n_neurons)
|
|
indices = list(indices)
|
|
indices.append(len(av))
|
|
av = np.array([
|
|
arr_to_num(av[i1:i2])
|
|
for i1, i2 in zip(indices[:-1], indices[1:])
|
|
])
|
|
else:
|
|
av = np.append(
|
|
av[:n_neurons/2],
|
|
av[-n_neurons/2:],
|
|
)
|
|
for activation, neuron in zip(av, layer.neurons):
|
|
neuron.set_fill(
|
|
color = self.neuron_fill_color,
|
|
opacity = activation
|
|
)
|
|
return layer
|
|
|
|
def activate_layers(self, input_vector):
|
|
activations = self.neural_network.get_activation_of_all_layers(input_vector)
|
|
for activation, layer in zip(activations, self.layers):
|
|
self.activate_layer(layer, activation)
|
|
|
|
def deactivate_layers(self):
|
|
all_neurons = VGroup(*it.chain(*[
|
|
layer.neurons
|
|
for layer in self.layers
|
|
]))
|
|
all_neurons.set_fill(opacity = 0)
|
|
return self
|
|
|
|
def get_edge_propogation_animations(self, index):
|
|
edge_group_copy = self.edge_groups[index].copy()
|
|
edge_group_copy.set_stroke(
|
|
self.edge_propogation_color,
|
|
width = 1.5*self.edge_stroke_width
|
|
)
|
|
return [ShowCreationThenDestruction(
|
|
edge_group_copy,
|
|
run_time = self.edge_propogation_time,
|
|
lag_ratio = 0.5
|
|
)]
|
|
|
|
def add_output_labels(self):
|
|
self.output_labels = VGroup()
|
|
for n, neuron in enumerate(self.layers[-1].neurons):
|
|
label = TexMobject(str(n))
|
|
label.set_height(0.75*neuron.get_height())
|
|
label.move_to(neuron)
|
|
label.shift(neuron.get_width()*RIGHT)
|
|
self.output_labels.add(label)
|
|
self.add(self.output_labels)
|
|
|
|
class MNistNetworkMobject(NetworkMobject):
|
|
CONFIG = {
|
|
"neuron_to_neuron_buff" : SMALL_BUFF,
|
|
"layer_to_layer_buff" : 1.5,
|
|
"edge_stroke_width" : 1,
|
|
"include_output_labels" : True,
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
network = get_pretrained_network()
|
|
NetworkMobject.__init__(self, network, **kwargs)
|
|
|
|
class NetworkScene(Scene):
|
|
CONFIG = {
|
|
"layer_sizes" : [8, 6, 6, 4],
|
|
"network_mob_config" : {},
|
|
}
|
|
def setup(self):
|
|
self.add_network()
|
|
|
|
def add_network(self):
|
|
self.network = Network(sizes = self.layer_sizes)
|
|
self.network_mob = NetworkMobject(
|
|
self.network,
|
|
**self.network_mob_config
|
|
)
|
|
self.add(self.network_mob)
|
|
|
|
def feed_forward(self, input_vector, false_confidence = False, added_anims = None):
|
|
if added_anims is None:
|
|
added_anims = []
|
|
activations = self.network.get_activation_of_all_layers(
|
|
input_vector
|
|
)
|
|
if false_confidence:
|
|
i = np.argmax(activations[-1])
|
|
activations[-1] *= 0
|
|
activations[-1][i] = 1.0
|
|
for i, activation in enumerate(activations):
|
|
self.show_activation_of_layer(i, activation, added_anims)
|
|
added_anims = []
|
|
|
|
def show_activation_of_layer(self, layer_index, activation_vector, added_anims = None):
|
|
if added_anims is None:
|
|
added_anims = []
|
|
layer = self.network_mob.layers[layer_index]
|
|
active_layer = self.network_mob.get_active_layer(
|
|
layer_index, activation_vector
|
|
)
|
|
anims = [Transform(layer, active_layer)]
|
|
if layer_index > 0:
|
|
anims += self.network_mob.get_edge_propogation_animations(
|
|
layer_index-1
|
|
)
|
|
anims += added_anims
|
|
self.play(*anims)
|
|
|
|
def remove_random_edges(self, prop = 0.9):
|
|
for edge_group in self.network_mob.edge_groups:
|
|
for edge in list(edge_group):
|
|
if np.random.random() < prop:
|
|
edge_group.remove(edge)
|
|
|
|
def make_transparent(image_mob):
|
|
alpha_vect = np.array(
|
|
image_mob.pixel_array[:,:,0],
|
|
dtype = 'uint8'
|
|
)
|
|
image_mob.set_color(WHITE)
|
|
image_mob.pixel_array[:,:,3] = alpha_vect
|
|
return image_mob
|
|
|
|
###############################
|
|
|
|
class ExampleThrees(PiCreatureScene):
|
|
def construct(self):
|
|
self.show_initial_three()
|
|
self.show_alternate_threes()
|
|
self.resolve_remaining_threes()
|
|
self.show_alternate_digits()
|
|
|
|
def show_initial_three(self):
|
|
randy = self.pi_creature
|
|
|
|
self.three_mobs = self.get_three_mobs()
|
|
three_mob = self.three_mobs[0]
|
|
three_mob_copy = three_mob[1].copy()
|
|
three_mob_copy.sort(lambda p : np.dot(p, DOWN+RIGHT))
|
|
|
|
braces = VGroup(*[Brace(three_mob, v) for v in (LEFT, UP)])
|
|
brace_labels = VGroup(*[
|
|
brace.get_text("28px")
|
|
for brace in braces
|
|
])
|
|
|
|
bubble = randy.get_bubble(height = 4, width = 6)
|
|
three_mob.generate_target()
|
|
three_mob.target.set_height(1)
|
|
three_mob.target.next_to(bubble[-1].get_left(), RIGHT, LARGE_BUFF)
|
|
arrow = Arrow(LEFT, RIGHT, color = BLUE)
|
|
arrow.next_to(three_mob.target, RIGHT)
|
|
real_three = TexMobject("3")
|
|
real_three.set_height(0.8)
|
|
real_three.next_to(arrow, RIGHT)
|
|
|
|
self.play(
|
|
FadeIn(three_mob[0]),
|
|
OldLaggedStart(FadeIn, three_mob[1])
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
OldLaggedStart(
|
|
DrawBorderThenFill, three_mob_copy,
|
|
run_time = 3,
|
|
stroke_color = WHITE,
|
|
remover = True,
|
|
),
|
|
randy.change, "sassy",
|
|
*it.chain(
|
|
list(map(GrowFromCenter, braces)),
|
|
list(map(FadeIn, brace_labels))
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(bubble),
|
|
MoveToTarget(three_mob),
|
|
FadeOut(braces),
|
|
FadeOut(brace_labels),
|
|
randy.change, "pondering"
|
|
)
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
Write(real_three)
|
|
)
|
|
self.wait()
|
|
|
|
self.bubble = bubble
|
|
self.arrow = arrow
|
|
self.real_three = real_three
|
|
|
|
def show_alternate_threes(self):
|
|
randy = self.pi_creature
|
|
|
|
three = self.three_mobs[0]
|
|
three.generate_target()
|
|
three.target[0].set_fill(opacity = 0, family = False)
|
|
for square in three.target[1]:
|
|
yellow_rgb = color_to_rgb(YELLOW)
|
|
square_rgb = color_to_rgb(square.get_fill_color())
|
|
square.set_fill(
|
|
rgba_to_color(yellow_rgb*square_rgb),
|
|
opacity = 0.5
|
|
)
|
|
|
|
alt_threes = VGroup(*self.three_mobs[1:])
|
|
alt_threes.arrange(DOWN)
|
|
alt_threes.set_height(FRAME_HEIGHT - 2)
|
|
alt_threes.to_edge(RIGHT)
|
|
|
|
for alt_three in alt_threes:
|
|
self.add(alt_three)
|
|
self.wait(0.5)
|
|
self.play(
|
|
randy.change, "plain",
|
|
*list(map(FadeOut, [
|
|
self.bubble, self.arrow, self.real_three
|
|
])) + [MoveToTarget(three)]
|
|
)
|
|
for alt_three in alt_threes[:2]:
|
|
self.play(three.replace, alt_three)
|
|
self.wait()
|
|
for moving_three in three, alt_threes[1]:
|
|
moving_three.generate_target()
|
|
moving_three.target.next_to(alt_threes, LEFT, LARGE_BUFF)
|
|
moving_three.target[0].set_stroke(width = 0)
|
|
moving_three.target[1].space_out_submobjects(1.5)
|
|
self.play(MoveToTarget(
|
|
moving_three, lag_ratio = 0.5
|
|
))
|
|
self.play(
|
|
Animation(randy),
|
|
moving_three.replace, randy.eyes[1],
|
|
moving_three.scale_in_place, 0.7,
|
|
run_time = 2,
|
|
lag_ratio = 0.5,
|
|
)
|
|
self.play(
|
|
Animation(randy),
|
|
FadeOut(moving_three)
|
|
)
|
|
|
|
self.remaining_threes = [alt_threes[0], alt_threes[2]]
|
|
|
|
def resolve_remaining_threes(self):
|
|
randy = self.pi_creature
|
|
|
|
left_three, right_three = self.remaining_threes
|
|
equals = TexMobject("=")
|
|
equals.move_to(self.arrow)
|
|
for three, vect in (left_three, LEFT), (right_three, RIGHT):
|
|
three.generate_target()
|
|
three.target.set_height(1)
|
|
three.target.next_to(equals, vect)
|
|
|
|
self.play(
|
|
randy.change, "thinking",
|
|
ShowCreation(self.bubble),
|
|
MoveToTarget(left_three),
|
|
MoveToTarget(right_three),
|
|
Write(equals),
|
|
)
|
|
self.wait()
|
|
|
|
self.equals = equals
|
|
|
|
def show_alternate_digits(self):
|
|
randy = self.pi_creature
|
|
cross = Cross(self.equals)
|
|
cross.stretch_to_fit_height(0.5)
|
|
three = self.remaining_threes[1]
|
|
|
|
image_map = get_organized_images()
|
|
arrays = [image_map[k][0] for k in range(8, 4, -1)]
|
|
alt_mobs = [
|
|
WrappedImage(
|
|
PixelsAsSquares(ImageMobject(layer_to_image_array(arr))),
|
|
color = LIGHT_GREY,
|
|
buff = 0
|
|
).replace(three)
|
|
for arr in arrays
|
|
]
|
|
|
|
self.play(
|
|
randy.change, "sassy",
|
|
Transform(three, alt_mobs[0]),
|
|
ShowCreation(cross)
|
|
)
|
|
self.wait()
|
|
for mob in alt_mobs[1:]:
|
|
self.play(Transform(three, mob))
|
|
self.wait()
|
|
|
|
######
|
|
|
|
def create_pi_creature(self):
|
|
return Randolph().to_corner(DOWN+LEFT)
|
|
|
|
def get_three_mobs(self):
|
|
three_arrays = get_organized_images()[3][:4]
|
|
three_mobs = VGroup()
|
|
for three_array in three_arrays:
|
|
im_mob = ImageMobject(
|
|
layer_to_image_array(three_array),
|
|
height = 4,
|
|
)
|
|
pixel_mob = PixelsAsSquares(im_mob)
|
|
three_mob = WrappedImage(
|
|
pixel_mob,
|
|
color = LIGHT_GREY,
|
|
buff = 0
|
|
)
|
|
three_mobs.add(three_mob)
|
|
return three_mobs
|
|
|
|
class BrainAndHow(Scene):
|
|
def construct(self):
|
|
brain = SVGMobject(file_name = "brain")
|
|
brain.set_height(2)
|
|
brain.set_fill(LIGHT_GREY)
|
|
brain_outline = brain.copy()
|
|
brain_outline.set_fill(opacity = 0)
|
|
brain_outline.set_stroke(BLUE_B, 3)
|
|
|
|
how = TextMobject("How?!?")
|
|
how.scale(2)
|
|
how.next_to(brain, UP)
|
|
|
|
self.add(brain)
|
|
self.play(Write(how))
|
|
for x in range(2):
|
|
self.play(
|
|
ShowPassingFlash(
|
|
brain_outline,
|
|
time_width = 0.5,
|
|
run_time = 2
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
class WriteAProgram(Scene):
|
|
def construct(self):
|
|
three_array = get_organized_images()[3][0]
|
|
im_mob = ImageMobject(layer_to_image_array(three_array))
|
|
three = PixelsAsSquares(im_mob)
|
|
three.sort(lambda p : np.dot(p, DOWN+RIGHT))
|
|
three.set_height(6)
|
|
three.next_to(ORIGIN, LEFT)
|
|
three_rect = SurroundingRectangle(
|
|
three,
|
|
color = BLUE,
|
|
buff = SMALL_BUFF
|
|
)
|
|
|
|
numbers = VGroup()
|
|
for square in three:
|
|
rgb = square.fill_rgb
|
|
num = DecimalNumber(
|
|
square.fill_rgb[0],
|
|
num_decimal_places = 1
|
|
)
|
|
num.set_stroke(width = 1)
|
|
color = rgba_to_color(1 - (rgb + 0.2)/1.2)
|
|
num.set_color(color)
|
|
num.set_width(0.7*square.get_width())
|
|
num.move_to(square)
|
|
numbers.add(num)
|
|
|
|
arrow = Arrow(LEFT, RIGHT, color = BLUE)
|
|
arrow.next_to(three, RIGHT)
|
|
|
|
choices = VGroup(*[TexMobject(str(n)) for n in range(10)])
|
|
choices.arrange(DOWN)
|
|
choices.set_height(FRAME_HEIGHT - 1)
|
|
choices.next_to(arrow, RIGHT)
|
|
|
|
self.play(
|
|
OldLaggedStart(DrawBorderThenFill, three),
|
|
ShowCreation(three_rect)
|
|
)
|
|
self.play(Write(numbers))
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
OldLaggedStart(FadeIn, choices),
|
|
)
|
|
|
|
rect = SurroundingRectangle(choices[0], buff = SMALL_BUFF)
|
|
q_mark = TexMobject("?")
|
|
q_mark.next_to(rect, RIGHT)
|
|
self.play(ShowCreation(rect))
|
|
for n in 8, 1, 5, 3:
|
|
self.play(
|
|
rect.move_to, choices[n],
|
|
MaintainPositionRelativeTo(q_mark, rect)
|
|
)
|
|
self.wait(1)
|
|
choice = choices[3]
|
|
choices.remove(choice)
|
|
choice.add(rect)
|
|
self.play(
|
|
choice.scale, 1.5,
|
|
choice.next_to, arrow, RIGHT,
|
|
FadeOut(choices),
|
|
FadeOut(q_mark),
|
|
)
|
|
self.wait(2)
|
|
|
|
class LayOutPlan(TeacherStudentsScene, NetworkScene):
|
|
def setup(self):
|
|
TeacherStudentsScene.setup(self)
|
|
NetworkScene.setup(self)
|
|
self.remove(self.network_mob)
|
|
|
|
def construct(self):
|
|
self.force_skipping()
|
|
|
|
self.show_words()
|
|
self.show_network()
|
|
self.show_math()
|
|
self.ask_about_layers()
|
|
self.show_learning()
|
|
self.show_videos()
|
|
|
|
def show_words(self):
|
|
words = VGroup(
|
|
TextMobject("Machine", "learning").set_color(GREEN),
|
|
TextMobject("Neural network").set_color(BLUE),
|
|
)
|
|
words.next_to(self.teacher.get_corner(UP+LEFT), UP)
|
|
words[0].save_state()
|
|
words[0].shift(DOWN)
|
|
words[0].fade(1)
|
|
|
|
self.play(
|
|
words[0].restore,
|
|
self.teacher.change, "raise_right_hand",
|
|
self.get_student_changes("pondering", "erm", "sassy")
|
|
)
|
|
self.play(
|
|
words[0].shift, MED_LARGE_BUFF*UP,
|
|
FadeIn(words[1]),
|
|
)
|
|
self.change_student_modes(
|
|
*["pondering"]*3,
|
|
look_at_arg = words
|
|
)
|
|
self.play(words.to_corner, UP+RIGHT)
|
|
|
|
self.words = words
|
|
|
|
def show_network(self):
|
|
network_mob = self.network_mob
|
|
network_mob.next_to(self.students, UP)
|
|
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(self.words[1].copy()),
|
|
network_mob.layers
|
|
),
|
|
self.get_student_changes(
|
|
*["confused"]*3,
|
|
lag_ratio = 0
|
|
),
|
|
self.teacher.change, "plain",
|
|
run_time = 1
|
|
)
|
|
self.play(ShowCreation(
|
|
network_mob.edge_groups,
|
|
lag_ratio = 0.5,
|
|
run_time = 2,
|
|
lag_factor = 8,
|
|
rate_func=linear,
|
|
))
|
|
in_vect = np.random.random(self.network.sizes[0])
|
|
self.feed_forward(in_vect)
|
|
|
|
def show_math(self):
|
|
equation = TexMobject(
|
|
"\\textbf{a}_{l+1}", "=",
|
|
"\\sigma(",
|
|
"W_l", "\\textbf{a}_l", "+", "b_l",
|
|
")"
|
|
)
|
|
equation.set_color_by_tex_to_color_map({
|
|
"\\textbf{a}" : GREEN,
|
|
})
|
|
equation.move_to(self.network_mob.get_corner(UP+RIGHT))
|
|
equation.to_edge(UP)
|
|
|
|
self.play(Write(equation, run_time = 2))
|
|
self.wait()
|
|
|
|
self.equation = equation
|
|
|
|
def ask_about_layers(self):
|
|
self.student_says(
|
|
"Why the layers?",
|
|
student_index = 2,
|
|
bubble_kwargs = {"direction" : LEFT}
|
|
)
|
|
self.wait()
|
|
self.play(RemovePiCreatureBubble(self.students[2]))
|
|
|
|
def show_learning(self):
|
|
word = self.words[0][1].copy()
|
|
rect = SurroundingRectangle(word, color = YELLOW)
|
|
self.network_mob.neuron_fill_color = YELLOW
|
|
|
|
layer = self.network_mob.layers[-1]
|
|
activation = np.zeros(len(layer.neurons))
|
|
activation[1] = 1.0
|
|
active_layer = self.network_mob.get_active_layer(
|
|
-1, activation
|
|
)
|
|
word_group = VGroup(word, rect)
|
|
word_group.generate_target()
|
|
word_group.target.move_to(self.equation, LEFT)
|
|
word_group.target[0].set_color(YELLOW)
|
|
word_group.target[1].set_stroke(width = 0)
|
|
|
|
self.play(ShowCreation(rect))
|
|
self.play(
|
|
Transform(layer, active_layer),
|
|
FadeOut(self.equation),
|
|
MoveToTarget(word_group),
|
|
)
|
|
for edge_group in reversed(self.network_mob.edge_groups):
|
|
edge_group.generate_target()
|
|
for edge in edge_group.target:
|
|
edge.set_stroke(
|
|
YELLOW,
|
|
width = 4*np.random.random()**2
|
|
)
|
|
self.play(MoveToTarget(edge_group))
|
|
self.wait()
|
|
|
|
self.learning_word = word
|
|
|
|
def show_videos(self):
|
|
network_mob = self.network_mob
|
|
learning = self.learning_word
|
|
structure = TextMobject("Structure")
|
|
structure.set_color(YELLOW)
|
|
videos = VGroup(*[
|
|
VideoIcon().set_fill(RED)
|
|
for x in range(2)
|
|
])
|
|
videos.set_height(1.5)
|
|
videos.arrange(RIGHT, buff = LARGE_BUFF)
|
|
videos.next_to(self.students, UP, LARGE_BUFF)
|
|
|
|
network_mob.generate_target()
|
|
network_mob.target.set_height(0.8*videos[0].get_height())
|
|
network_mob.target.move_to(videos[0])
|
|
learning.generate_target()
|
|
learning.target.next_to(videos[1], UP)
|
|
structure.next_to(videos[0], UP)
|
|
structure.shift(0.5*SMALL_BUFF*UP)
|
|
|
|
self.revert_to_original_skipping_status()
|
|
self.play(
|
|
MoveToTarget(network_mob),
|
|
MoveToTarget(learning)
|
|
)
|
|
self.play(
|
|
DrawBorderThenFill(videos[0]),
|
|
FadeIn(structure),
|
|
self.get_student_changes(*["pondering"]*3)
|
|
)
|
|
self.wait()
|
|
self.play(DrawBorderThenFill(videos[1]))
|
|
self.wait()
|
|
|
|
class PreviewMNistNetwork(NetworkScene):
|
|
CONFIG = {
|
|
"n_examples" : 15,
|
|
"network_mob_config" : {},
|
|
}
|
|
def construct(self):
|
|
self.remove_random_edges(0.7) #Remove?
|
|
|
|
training_data, validation_data, test_data = load_data_wrapper()
|
|
for data in test_data[:self.n_examples]:
|
|
self.feed_in_image(data[0])
|
|
|
|
def feed_in_image(self, in_vect):
|
|
image = PixelsFromVect(in_vect)
|
|
image.next_to(self.network_mob, LEFT, LARGE_BUFF, UP)
|
|
image.shift_onto_screen()
|
|
image_rect = SurroundingRectangle(image, color = BLUE)
|
|
start_neurons = self.network_mob.layers[0].neurons.copy()
|
|
start_neurons.set_stroke(WHITE, width = 0)
|
|
start_neurons.set_fill(WHITE, 0)
|
|
|
|
self.play(FadeIn(image), FadeIn(image_rect))
|
|
self.feed_forward(in_vect, added_anims = [
|
|
self.get_image_to_layer_one_animation(image, start_neurons)
|
|
])
|
|
n = np.argmax([
|
|
neuron.get_fill_opacity()
|
|
for neuron in self.network_mob.layers[-1].neurons
|
|
])
|
|
rect = SurroundingRectangle(VGroup(
|
|
self.network_mob.layers[-1].neurons[n],
|
|
self.network_mob.output_labels[n],
|
|
))
|
|
self.play(ShowCreation(rect))
|
|
self.reset_display(rect, image, image_rect)
|
|
|
|
def reset_display(self, answer_rect, image, image_rect):
|
|
self.play(FadeOut(answer_rect))
|
|
self.play(
|
|
FadeOut(image),
|
|
FadeOut(image_rect),
|
|
self.network_mob.deactivate_layers,
|
|
)
|
|
|
|
def get_image_to_layer_one_animation(self, image, start_neurons):
|
|
image_mover = VGroup(*[
|
|
pixel.copy()
|
|
for pixel in image
|
|
if pixel.fill_rgb[0] > 0.1
|
|
])
|
|
return Transform(
|
|
image_mover, start_neurons,
|
|
remover = True,
|
|
run_time = 1,
|
|
)
|
|
|
|
###
|
|
|
|
def add_network(self):
|
|
self.network_mob = MNistNetworkMobject(**self.network_mob_config)
|
|
self.network = self.network_mob.neural_network
|
|
self.add(self.network_mob)
|
|
|
|
class AlternateNeuralNetworks(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
examples = VGroup(
|
|
VGroup(
|
|
TextMobject("Convolutional neural network"),
|
|
TextMobject("Good for image recognition"),
|
|
),
|
|
VGroup(
|
|
TextMobject("Long short-term memory network"),
|
|
TextMobject("Good for speech recognition"),
|
|
)
|
|
)
|
|
for ex in examples:
|
|
arrow = Arrow(LEFT, RIGHT, color = BLUE)
|
|
ex[0].next_to(arrow, LEFT)
|
|
ex[1].next_to(arrow, RIGHT)
|
|
ex.submobjects.insert(1, arrow)
|
|
examples.set_width(FRAME_WIDTH - 1)
|
|
examples.next_to(morty, UP).to_edge(RIGHT)
|
|
|
|
maybe_words = TextMobject("Maybe future videos?")
|
|
maybe_words.scale(0.8)
|
|
maybe_words.next_to(morty, UP)
|
|
maybe_words.to_edge(RIGHT)
|
|
maybe_words.set_color(YELLOW)
|
|
|
|
self.play(
|
|
Write(examples[0], run_time = 2),
|
|
morty.change, "raise_right_hand"
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
examples[0].shift, MED_LARGE_BUFF*UP,
|
|
FadeIn(examples[1], lag_ratio = 0.5),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
examples.shift, UP,
|
|
FadeIn(maybe_words),
|
|
morty.change, "maybe"
|
|
)
|
|
self.wait(2)
|
|
|
|
class PlainVanillaWrapper(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Plain vanilla")
|
|
subtitle = TextMobject("(aka ``multilayer perceptron'')")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
subtitle.next_to(title, DOWN)
|
|
|
|
self.add(title)
|
|
self.wait(2)
|
|
self.play(Write(subtitle, run_time = 2))
|
|
self.wait(2)
|
|
|
|
class NotPerfectAddOn(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Not perfect!")
|
|
words.scale(1.5)
|
|
arrow = Arrow(UP+RIGHT, DOWN+LEFT, color = RED)
|
|
words.set_color(RED)
|
|
arrow.to_corner(DOWN+LEFT)
|
|
words.next_to(arrow, UP+RIGHT)
|
|
|
|
self.play(
|
|
Write(words),
|
|
ShowCreation(arrow),
|
|
run_time = 1
|
|
)
|
|
self.wait(2)
|
|
|
|
class MoreAThanI(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says(
|
|
"More \\\\ A than I",
|
|
target_mode = "hesitant"
|
|
)
|
|
self.change_student_modes("sad", "erm", "tired")
|
|
self.wait(2)
|
|
|
|
class BreakDownName(Scene):
|
|
def construct(self):
|
|
self.ask_questions()
|
|
self.show_neuron()
|
|
|
|
def ask_questions(self):
|
|
name = TextMobject("Neural", "network")
|
|
name.to_edge(UP)
|
|
q1 = TextMobject(
|
|
"What are \\\\ the ", "neuron", "s?",
|
|
arg_separator = ""
|
|
)
|
|
q2 = TextMobject("How are \\\\ they connected?")
|
|
q1.next_to(name[0].get_bottom(), DOWN, buff = LARGE_BUFF)
|
|
q2.next_to(name[1].get_bottom(), DOWN+RIGHT, buff = LARGE_BUFF)
|
|
a1 = Arrow(q1.get_top(), name[0].get_bottom())
|
|
a2 = Arrow(q2.get_top(), name.get_corner(DOWN+RIGHT))
|
|
VGroup(q1, a1).set_color(BLUE)
|
|
VGroup(q2, a2).set_color(YELLOW)
|
|
|
|
randy = Randolph().to_corner(DOWN+LEFT)
|
|
brain = SVGMobject(file_name = "brain")
|
|
brain.set_fill(LIGHT_GREY, opacity = 0)
|
|
brain.replace(randy.eyes, dim_to_match = 1)
|
|
|
|
self.add(name)
|
|
self.play(randy.change, "pondering")
|
|
self.play(
|
|
brain.set_height, 2,
|
|
brain.shift, 2*UP,
|
|
brain.set_fill, None, 1,
|
|
randy.look, UP
|
|
)
|
|
brain_outline = brain.copy()
|
|
brain_outline.set_fill(opacity = 0)
|
|
brain_outline.set_stroke(BLUE_B, 3)
|
|
self.play(
|
|
ShowPassingFlash(
|
|
brain_outline,
|
|
time_width = 0.5,
|
|
run_time = 2
|
|
)
|
|
)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(
|
|
Write(q1, run_time = 1),
|
|
ShowCreation(a1),
|
|
name[0].set_color, q1.get_color(),
|
|
)
|
|
self.play(
|
|
Write(q2, run_time = 1),
|
|
ShowCreation(a2),
|
|
name[1].set_color, q2.get_color()
|
|
)
|
|
self.wait(2)
|
|
|
|
self.play(*list(map(FadeOut, [
|
|
name, randy, brain,
|
|
q2, a1, a2,
|
|
q1[0], q1[2]
|
|
])))
|
|
|
|
self.neuron_word = q1[1]
|
|
|
|
def show_neuron(self):
|
|
neuron_word = TextMobject("Neuron")
|
|
arrow = TexMobject("\\rightarrow")
|
|
arrow.shift(LEFT)
|
|
description = TextMobject("Thing that holds a number")
|
|
neuron_word.set_color(BLUE)
|
|
neuron_word.next_to(arrow, LEFT)
|
|
neuron_word.shift(0.5*SMALL_BUFF*UP)
|
|
description.next_to(arrow, RIGHT)
|
|
|
|
neuron = Circle(radius = 0.35, color = BLUE)
|
|
neuron.next_to(neuron_word, UP, MED_LARGE_BUFF)
|
|
num = TexMobject("0.2")
|
|
num.set_width(0.7*neuron.get_width())
|
|
num.move_to(neuron)
|
|
num.save_state()
|
|
num.move_to(description.get_right())
|
|
num.set_fill(opacity = 1)
|
|
|
|
self.play(
|
|
ReplacementTransform(self.neuron_word, neuron_word),
|
|
ShowCreation(neuron)
|
|
)
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
Write(description, run_time = 1)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
neuron.set_fill, None, 0.2,
|
|
num.restore
|
|
)
|
|
self.wait()
|
|
for value in 0.8, 0.4, 0.1, 0.5:
|
|
mob = TexMobject(str(value))
|
|
mob.replace(num)
|
|
self.play(
|
|
neuron.set_fill, None, value,
|
|
Transform(num, mob)
|
|
)
|
|
self.wait()
|
|
|
|
class IntroduceEachLayer(PreviewMNistNetwork):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"neuron_stroke_color" : WHITE,
|
|
"neuron_stroke_width" : 2,
|
|
"neuron_fill_color" : WHITE,
|
|
"average_shown_activation_of_large_layer" : False,
|
|
"edge_propogation_color" : YELLOW,
|
|
"edge_propogation_time" : 2,
|
|
}
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.break_up_image_as_neurons()
|
|
self.show_activation_of_one_neuron()
|
|
self.transform_into_full_network()
|
|
self.show_output_layer()
|
|
self.show_hidden_layers()
|
|
self.show_propogation()
|
|
|
|
def setup_network_mob(self):
|
|
self.remove(self.network_mob)
|
|
|
|
def break_up_image_as_neurons(self):
|
|
self.image_map = get_organized_images()
|
|
image = self.image_map[9][0]
|
|
image_mob = PixelsFromVect(image)
|
|
image_mob.set_height(4)
|
|
image_mob.next_to(ORIGIN, LEFT)
|
|
rect = SurroundingRectangle(image_mob, color = BLUE)
|
|
neurons = VGroup()
|
|
for pixel in image_mob:
|
|
pixel.set_fill(WHITE, opacity = pixel.fill_rgb[0])
|
|
neuron = Circle(
|
|
color = WHITE,
|
|
stroke_width = 1,
|
|
radius = pixel.get_width()/2
|
|
)
|
|
neuron.move_to(pixel)
|
|
neuron.set_fill(WHITE, pixel.get_fill_opacity())
|
|
neurons.add(neuron)
|
|
neurons.scale_in_place(1.2)
|
|
neurons.space_out_submobjects(1.3)
|
|
neurons.to_edge(DOWN)
|
|
|
|
braces = VGroup(*[Brace(neurons, vect) for vect in (LEFT, UP)])
|
|
labels = VGroup(*[
|
|
brace.get_tex("28", buff = SMALL_BUFF)
|
|
for brace in braces
|
|
])
|
|
|
|
equation = TexMobject("28", "\\times", "28", "=", "784")
|
|
equation.next_to(neurons, RIGHT, LARGE_BUFF, UP)
|
|
|
|
self.corner_image = MNistMobject(image)
|
|
self.corner_image.to_corner(UP+LEFT)
|
|
|
|
self.add(image_mob, rect)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(image_mob, neurons),
|
|
FadeOut(rect),
|
|
FadeIn(braces),
|
|
FadeIn(labels),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(labels[0].copy(), equation[0]),
|
|
Write(equation[1]),
|
|
ReplacementTransform(labels[1].copy(), equation[2]),
|
|
Write(equation[3]),
|
|
Write(equation[4]),
|
|
)
|
|
self.wait()
|
|
|
|
self.neurons = neurons
|
|
self.braces = braces
|
|
self.brace_labels = labels
|
|
self.num_pixels_equation = equation
|
|
self.image_vect = image
|
|
|
|
def show_activation_of_one_neuron(self):
|
|
neurons = self.neurons
|
|
numbers = VGroup()
|
|
example_neuron = None
|
|
example_num = None
|
|
for neuron in neurons:
|
|
o = neuron.get_fill_opacity()
|
|
num = DecimalNumber(o, num_decimal_places = 1)
|
|
num.set_width(0.7*neuron.get_width())
|
|
num.move_to(neuron)
|
|
if o > 0.8:
|
|
num.set_fill(BLACK)
|
|
numbers.add(num)
|
|
if o > 0.25 and o < 0.75 and example_neuron is None:
|
|
example_neuron = neuron
|
|
example_num = num
|
|
example_neuron.save_state()
|
|
example_num.save_state()
|
|
example_neuron.generate_target()
|
|
example_neuron.target.set_height(1.5)
|
|
example_neuron.target.next_to(neurons, RIGHT)
|
|
example_num.target = DecimalNumber(
|
|
example_neuron.get_fill_opacity()
|
|
)
|
|
example_num.target.move_to(example_neuron.target)
|
|
|
|
def change_activation(num):
|
|
self.play(
|
|
example_neuron.set_fill, None, num,
|
|
ChangingDecimal(
|
|
example_num,
|
|
lambda a : example_neuron.get_fill_opacity(),
|
|
),
|
|
UpdateFromFunc(
|
|
example_num,
|
|
lambda m : m.set_fill(
|
|
BLACK if example_neuron.get_fill_opacity() > 0.8 else WHITE
|
|
)
|
|
)
|
|
)
|
|
|
|
self.play(OldLaggedStart(FadeIn, numbers))
|
|
self.play(
|
|
MoveToTarget(example_neuron),
|
|
MoveToTarget(example_num)
|
|
)
|
|
self.wait()
|
|
curr_opacity = example_neuron.get_fill_opacity()
|
|
for num in 0.3, 0.01, 1.0, curr_opacity:
|
|
change_activation(num)
|
|
self.wait()
|
|
|
|
rect = SurroundingRectangle(example_num, color = YELLOW)
|
|
activation = TextMobject("``Activation''")
|
|
activation.next_to(example_neuron, RIGHT)
|
|
activation.set_color(rect.get_color())
|
|
self.play(ShowCreation(rect))
|
|
self.play(Write(activation, run_time = 1))
|
|
self.wait()
|
|
change_activation(1.0)
|
|
self.wait()
|
|
change_activation(0.2)
|
|
self.wait()
|
|
|
|
self.play(
|
|
example_neuron.restore,
|
|
example_num.restore,
|
|
FadeOut(activation),
|
|
FadeOut(rect),
|
|
)
|
|
self.play(FadeOut(numbers))
|
|
|
|
def transform_into_full_network(self):
|
|
network_mob = self.network_mob
|
|
neurons = self.neurons
|
|
layer = network_mob.layers[0]
|
|
layer.save_state()
|
|
layer.rotate(np.pi/2)
|
|
layer.center()
|
|
layer.brace_label.rotate_in_place(-np.pi/2)
|
|
n = network_mob.max_shown_neurons/2
|
|
|
|
rows = VGroup(*[
|
|
VGroup(*neurons[28*i:28*(i+1)])
|
|
for i in range(28)
|
|
])
|
|
|
|
self.play(
|
|
FadeOut(self.braces),
|
|
FadeOut(self.brace_labels),
|
|
FadeOut(VGroup(*self.num_pixels_equation[:-1]))
|
|
)
|
|
|
|
self.play(rows.space_out_submobjects, 1.2)
|
|
self.play(
|
|
rows.arrange, RIGHT, buff = SMALL_BUFF,
|
|
path_arc = np.pi/2,
|
|
run_time = 2
|
|
)
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(*neurons[:n]),
|
|
VGroup(*layer.neurons[:n]),
|
|
),
|
|
ReplacementTransform(
|
|
VGroup(*neurons[n:-n]),
|
|
layer.dots,
|
|
),
|
|
ReplacementTransform(
|
|
VGroup(*neurons[-n:]),
|
|
VGroup(*layer.neurons[-n:]),
|
|
),
|
|
)
|
|
self.play(
|
|
ReplacementTransform(
|
|
self.num_pixels_equation[-1],
|
|
layer.brace_label
|
|
),
|
|
FadeIn(layer.brace),
|
|
)
|
|
self.play(layer.restore, FadeIn(self.corner_image))
|
|
self.wait()
|
|
for edge_group, layer in zip(network_mob.edge_groups, network_mob.layers[1:]):
|
|
self.play(
|
|
OldLaggedStart(FadeIn, layer, run_time = 1),
|
|
ShowCreation(edge_group),
|
|
)
|
|
self.wait()
|
|
|
|
def show_output_layer(self):
|
|
layer = self.network_mob.layers[-1]
|
|
labels = self.network_mob.output_labels
|
|
rect = SurroundingRectangle(
|
|
VGroup(layer, labels)
|
|
)
|
|
neuron = layer.neurons[-1]
|
|
neuron.set_fill(WHITE, 0)
|
|
label = labels[-1]
|
|
for mob in neuron, label:
|
|
mob.save_state()
|
|
mob.generate_target()
|
|
neuron.target.scale_in_place(4)
|
|
neuron.target.shift(1.5*RIGHT)
|
|
label.target.scale(1.5)
|
|
label.target.next_to(neuron.target, RIGHT)
|
|
|
|
activation = DecimalNumber(0)
|
|
activation.move_to(neuron.target)
|
|
|
|
def change_activation(num):
|
|
self.play(
|
|
neuron.set_fill, None, num,
|
|
ChangingDecimal(
|
|
activation,
|
|
lambda a : neuron.get_fill_opacity(),
|
|
),
|
|
UpdateFromFunc(
|
|
activation,
|
|
lambda m : m.set_fill(
|
|
BLACK if neuron.get_fill_opacity() > 0.8 else WHITE
|
|
)
|
|
)
|
|
)
|
|
|
|
self.play(ShowCreation(rect))
|
|
self.play(OldLaggedStart(FadeIn, labels))
|
|
self.wait()
|
|
self.play(
|
|
MoveToTarget(neuron),
|
|
MoveToTarget(label),
|
|
)
|
|
self.play(FadeIn(activation))
|
|
for num in 0.5, 0.38, 0.97:
|
|
change_activation(num)
|
|
self.wait()
|
|
self.play(
|
|
neuron.restore,
|
|
neuron.set_fill, None, 1,
|
|
label.restore,
|
|
FadeOut(activation),
|
|
FadeOut(rect),
|
|
)
|
|
self.wait()
|
|
|
|
def show_hidden_layers(self):
|
|
hidden_layers = VGroup(*self.network_mob.layers[1:3])
|
|
rect = SurroundingRectangle(hidden_layers, color = YELLOW)
|
|
name = TextMobject("``Hidden layers''")
|
|
name.next_to(rect, UP, SMALL_BUFF)
|
|
name.set_color(YELLOW)
|
|
q_marks = VGroup()
|
|
for layer in hidden_layers:
|
|
for neuron in layer.neurons:
|
|
q_mark = TextMobject("?")
|
|
q_mark.set_height(0.8*neuron.get_height())
|
|
q_mark.move_to(neuron)
|
|
q_marks.add(q_mark)
|
|
q_marks.set_color_by_gradient(BLUE, YELLOW)
|
|
q_mark = TextMobject("?").scale(4)
|
|
q_mark.move_to(hidden_layers)
|
|
q_mark.set_color(YELLOW)
|
|
q_marks.add(q_mark)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(name)
|
|
)
|
|
self.wait()
|
|
self.play(Write(q_marks))
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(q_marks),
|
|
Animation(q_marks[-1].copy())
|
|
)
|
|
|
|
def show_propogation(self):
|
|
self.revert_to_original_skipping_status()
|
|
self.remove_random_edges(0.7)
|
|
self.feed_forward(self.image_vect)
|
|
|
|
class DiscussChoiceForHiddenLayers(TeacherStudentsScene):
|
|
def construct(self):
|
|
network_mob = MNistNetworkMobject(
|
|
layer_to_layer_buff = 2.5,
|
|
neuron_stroke_color = WHITE,
|
|
)
|
|
network_mob.set_height(4)
|
|
network_mob.to_edge(UP, buff = LARGE_BUFF)
|
|
layers = VGroup(*network_mob.layers[1:3])
|
|
rects = VGroup(*list(map(SurroundingRectangle, layers)))
|
|
self.add(network_mob)
|
|
|
|
two_words = TextMobject("2 hidden layers")
|
|
two_words.set_color(YELLOW)
|
|
sixteen_words = TextMobject("16 neurons each")
|
|
sixteen_words.set_color(MAROON_B)
|
|
for words in two_words, sixteen_words:
|
|
words.next_to(rects, UP)
|
|
|
|
neurons_anim = OldLaggedStart(
|
|
Indicate,
|
|
VGroup(*it.chain(*[layer.neurons for layer in layers])),
|
|
rate_func = there_and_back,
|
|
scale_factor = 2,
|
|
color = MAROON_B,
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(rects),
|
|
Write(two_words, run_time = 1),
|
|
self.teacher.change, "raise_right_hand",
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(rects),
|
|
ReplacementTransform(two_words, sixteen_words),
|
|
neurons_anim
|
|
)
|
|
self.wait()
|
|
self.play(self.teacher.change, "shruggie")
|
|
self.change_student_modes("erm", "confused", "sassy")
|
|
self.wait()
|
|
self.student_says(
|
|
"Why 2 \\\\ layers?",
|
|
student_index = 1,
|
|
bubble_kwargs = {"direction" : RIGHT},
|
|
run_time = 1,
|
|
target_mode = "raise_left_hand",
|
|
)
|
|
self.play(self.teacher.change, "happy")
|
|
self.wait()
|
|
self.student_says(
|
|
"Why 16?",
|
|
student_index = 0,
|
|
run_time = 1,
|
|
)
|
|
self.play(neurons_anim, run_time = 3)
|
|
self.play(
|
|
self.teacher.change, "shruggie",
|
|
RemovePiCreatureBubble(self.students[0]),
|
|
)
|
|
self.wait()
|
|
|
|
class MoreHonestMNistNetworkPreview(IntroduceEachLayer):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"edge_propogation_time" : 1.5,
|
|
}
|
|
}
|
|
def construct(self):
|
|
PreviewMNistNetwork.construct(self)
|
|
|
|
def get_image_to_layer_one_animation(self, image, start_neurons):
|
|
neurons = VGroup()
|
|
for pixel in image:
|
|
neuron = Circle(
|
|
radius = pixel.get_width()/2,
|
|
stroke_width = 1,
|
|
stroke_color = WHITE,
|
|
fill_color = WHITE,
|
|
fill_opacity = pixel.fill_rgb[0]
|
|
)
|
|
neuron.move_to(pixel)
|
|
neurons.add(neuron)
|
|
neurons.scale(1.2)
|
|
neurons.next_to(image, DOWN)
|
|
n = len(start_neurons)
|
|
point = VectorizedPoint(start_neurons.get_center())
|
|
target = VGroup(*it.chain(
|
|
start_neurons[:n/2],
|
|
[point.copy() for x in range(len(neurons)-n)],
|
|
start_neurons[n/2:],
|
|
))
|
|
mover = image.copy()
|
|
self.play(Transform(mover, neurons))
|
|
return Transform(
|
|
mover, target,
|
|
run_time = 2,
|
|
lag_ratio = 0.5,
|
|
remover = True
|
|
)
|
|
|
|
class AskAboutPropogationAndTraining(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"How does one layer \\\\ influence the next?",
|
|
student_index = 0,
|
|
run_time = 1
|
|
)
|
|
self.wait()
|
|
self.student_says(
|
|
"How does \\\\ training work?",
|
|
student_index = 2,
|
|
run_time = 1
|
|
)
|
|
self.wait(3)
|
|
|
|
class AskAboutLayers(PreviewMNistNetwork):
|
|
def construct(self):
|
|
self.play(
|
|
self.network_mob.scale, 0.8,
|
|
self.network_mob.to_edge, DOWN,
|
|
)
|
|
|
|
question = TextMobject("Why the", "layers?")
|
|
question.to_edge(UP)
|
|
neuron_groups = [
|
|
layer.neurons
|
|
for layer in self.network_mob.layers
|
|
]
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
question[1].get_bottom(),
|
|
group.get_top()
|
|
)
|
|
for group in neuron_groups
|
|
])
|
|
rects = list(map(SurroundingRectangle, neuron_groups[1:3]))
|
|
|
|
self.play(
|
|
Write(question, run_time = 1),
|
|
OldLaggedStart(
|
|
GrowFromPoint, arrows,
|
|
lambda a : (a, a.get_start()),
|
|
run_time = 2
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(ShowCreation, rects)))
|
|
self.wait()
|
|
|
|
class BreakUpMacroPatterns(IntroduceEachLayer):
|
|
CONFIG = {
|
|
"camera_config" : {"background_opacity" : 1},
|
|
"prefixes" : [
|
|
"nine", "eight", "four",
|
|
"upper_loop", "right_line",
|
|
"lower_loop", "horizontal_line",
|
|
"upper_left_line"
|
|
]
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.setup_needed_patterns()
|
|
self.setup_added_patterns()
|
|
self.show_nine()
|
|
self.show_eight()
|
|
self.show_four()
|
|
self.show_second_to_last_layer()
|
|
self.show_upper_loop_activation()
|
|
self.show_what_learning_is_required()
|
|
|
|
def setup_needed_patterns(self):
|
|
prefixes = self.prefixes
|
|
vects = [
|
|
np.array(Image.open(
|
|
get_full_raster_image_path("handwritten_" + p),
|
|
))[:,:,0].flatten()/255.0
|
|
for p in prefixes
|
|
]
|
|
mobjects = list(map(MNistMobject, vects))
|
|
for mob in mobjects:
|
|
image = mob[1]
|
|
self.make_transparent(image)
|
|
for prefix, mob in zip(prefixes, mobjects):
|
|
setattr(self, prefix, mob)
|
|
|
|
def setup_added_patterns(self):
|
|
image_map = get_organized_images()
|
|
two, three, five = mobs = [
|
|
MNistMobject(image_map[n][0])
|
|
for n in (2, 3, 5)
|
|
]
|
|
self.added_patterns = VGroup()
|
|
for mob in mobs:
|
|
for i, j in it.product([0, 14], [0, 14]):
|
|
pattern = mob.deepcopy()
|
|
pa = pattern[1].pixel_array
|
|
temp = np.array(pa[i:i+14,j:j+14,:], dtype = 'uint8')
|
|
pa[:,:] = 0
|
|
pa[i:i+14,j:j+14,:] = temp
|
|
self.make_transparent(pattern[1])
|
|
pattern[1].set_color(random_bright_color())
|
|
self.added_patterns.add(pattern)
|
|
self.image_map = image_map
|
|
|
|
def show_nine(self):
|
|
nine = self.nine
|
|
upper_loop = self.upper_loop
|
|
right_line = self.right_line
|
|
equation = self.get_equation(nine, upper_loop, right_line)
|
|
equation.to_edge(UP)
|
|
equation.shift(LEFT)
|
|
|
|
parts = [upper_loop[1], right_line[1]]
|
|
for mob, color in zip(parts, [YELLOW, RED]):
|
|
mob.set_color(color)
|
|
mob.save_state()
|
|
mob.move_to(nine)
|
|
right_line[1].pixel_array[:14,:,3] = 0
|
|
|
|
self.play(FadeIn(nine))
|
|
self.wait()
|
|
self.play(*list(map(FadeIn, parts)))
|
|
self.wait()
|
|
self.play(
|
|
Write(equation[1]),
|
|
upper_loop[1].restore,
|
|
FadeIn(upper_loop[0])
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(equation[3]),
|
|
right_line[1].restore,
|
|
FadeIn(right_line[0]),
|
|
)
|
|
self.wait()
|
|
|
|
self.nine_equation = equation
|
|
|
|
def show_eight(self):
|
|
eight = self.eight
|
|
upper_loop = self.upper_loop.deepcopy()
|
|
lower_loop = self.lower_loop
|
|
lower_loop[1].set_color(GREEN)
|
|
|
|
equation = self.get_equation(eight, upper_loop, lower_loop)
|
|
equation.next_to(self.nine_equation, DOWN)
|
|
|
|
lower_loop[1].save_state()
|
|
lower_loop[1].move_to(eight[1])
|
|
|
|
self.play(
|
|
FadeIn(eight),
|
|
Write(equation[1]),
|
|
)
|
|
self.play(ReplacementTransform(
|
|
self.upper_loop.copy(),
|
|
upper_loop
|
|
))
|
|
self.wait()
|
|
self.play(FadeIn(lower_loop[1]))
|
|
self.play(
|
|
Write(equation[3]),
|
|
lower_loop[1].restore,
|
|
FadeIn(lower_loop[0]),
|
|
)
|
|
self.wait()
|
|
|
|
self.eight_equation = equation
|
|
|
|
def show_four(self):
|
|
four = self.four
|
|
upper_left_line = self.upper_left_line
|
|
upper_left_line[1].set_color(BLUE)
|
|
horizontal_line = self.horizontal_line
|
|
horizontal_line[1].set_color(MAROON_B)
|
|
right_line = self.right_line.deepcopy()
|
|
equation = self.get_equation(four, right_line, upper_left_line, horizontal_line)
|
|
equation.next_to(
|
|
self.eight_equation, DOWN, aligned_edge = LEFT
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(four),
|
|
Write(equation[1])
|
|
)
|
|
self.play(ReplacementTransform(
|
|
self.right_line.copy(), right_line
|
|
))
|
|
self.play(OldLaggedStart(
|
|
FadeIn, VGroup(*equation[3:])
|
|
))
|
|
self.wait(2)
|
|
|
|
self.four_equation = equation
|
|
|
|
def show_second_to_last_layer(self):
|
|
everything = VGroup(*it.chain(
|
|
self.nine_equation,
|
|
self.eight_equation,
|
|
self.four_equation,
|
|
))
|
|
patterns = VGroup(
|
|
self.upper_loop,
|
|
self.lower_loop,
|
|
self.right_line,
|
|
self.upper_left_line,
|
|
self.horizontal_line,
|
|
*self.added_patterns[:11]
|
|
)
|
|
for pattern in patterns:
|
|
pattern.add_to_back(
|
|
pattern[1].copy().set_color(BLACK, alpha = 1)
|
|
)
|
|
everything.remove(*patterns)
|
|
network_mob = self.network_mob
|
|
layer = network_mob.layers[-2]
|
|
patterns.generate_target()
|
|
for pattern, neuron in zip(patterns.target, layer.neurons):
|
|
pattern.set_height(neuron.get_height())
|
|
pattern.next_to(neuron, RIGHT, SMALL_BUFF)
|
|
for pattern in patterns[5:]:
|
|
pattern.fade(1)
|
|
|
|
self.play(*list(map(FadeOut, everything)))
|
|
self.play(
|
|
FadeIn(
|
|
network_mob,
|
|
lag_ratio = 0.5,
|
|
run_time = 3,
|
|
),
|
|
MoveToTarget(patterns)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.patterns = patterns
|
|
|
|
def show_upper_loop_activation(self):
|
|
neuron = self.network_mob.layers[-2].neurons[0]
|
|
words = TextMobject("Upper loop neuron...maybe...")
|
|
words.scale(0.8)
|
|
words.next_to(neuron, UP)
|
|
words.shift(RIGHT)
|
|
rect = SurroundingRectangle(VGroup(
|
|
neuron, self.patterns[0]
|
|
))
|
|
nine = self.nine
|
|
upper_loop = self.upper_loop.copy()
|
|
upper_loop.remove(upper_loop[0])
|
|
upper_loop.replace(nine)
|
|
nine.add(upper_loop)
|
|
nine.to_corner(UP+LEFT)
|
|
self.remove_random_edges(0.7)
|
|
self.network.get_activation_of_all_layers = lambda v : [
|
|
np.zeros(784),
|
|
sigmoid(6*(np.random.random(16)-0.5)),
|
|
np.array([1, 0, 1] + 13*[0]),
|
|
np.array(9*[0] + [1])
|
|
]
|
|
|
|
self.play(FadeIn(nine))
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(words)
|
|
)
|
|
self.add_foreground_mobject(self.patterns)
|
|
self.feed_forward(np.random.random(784))
|
|
self.wait(2)
|
|
|
|
def show_what_learning_is_required(self):
|
|
edge_group = self.network_mob.edge_groups[-1].copy()
|
|
edge_group.set_stroke(YELLOW, 4)
|
|
for x in range(3):
|
|
self.play(OldLaggedStart(
|
|
ShowCreationThenDestruction, edge_group,
|
|
run_time = 3
|
|
))
|
|
self.wait()
|
|
|
|
######
|
|
|
|
def get_equation(self, *mobs):
|
|
equation = VGroup(
|
|
mobs[0], TexMobject("=").scale(2),
|
|
*list(it.chain(*[
|
|
[m, TexMobject("+").scale(2)]
|
|
for m in mobs[1:-1]
|
|
])) + [mobs[-1]]
|
|
)
|
|
equation.arrange(RIGHT)
|
|
return equation
|
|
|
|
def make_transparent(self, image_mob):
|
|
return make_transparent(image_mob)
|
|
alpha_vect = np.array(
|
|
image_mob.pixel_array[:,:,0],
|
|
dtype = 'uint8'
|
|
)
|
|
image_mob.set_color(WHITE)
|
|
image_mob.pixel_array[:,:,3] = alpha_vect
|
|
return image_mob
|
|
|
|
class GenerallyLoopyPattern(Scene):
|
|
def construct(self):
|
|
image_map = get_organized_images()
|
|
images = list(map(MNistMobject, it.chain(
|
|
image_map[8], image_map[9],
|
|
)))
|
|
random.shuffle(images)
|
|
|
|
for image in images:
|
|
image.to_corner(DOWN+RIGHT)
|
|
self.add(image)
|
|
self.wait(0.2)
|
|
self.remove(image)
|
|
|
|
class HowWouldYouRecognizeSubcomponent(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"Okay, but recognizing loops \\\\",
|
|
"is just as hard!",
|
|
target_mode = "sassy"
|
|
)
|
|
self.play(
|
|
self.teacher.change, "guilty"
|
|
)
|
|
self.wait()
|
|
|
|
class BreakUpMicroPatterns(BreakUpMacroPatterns):
|
|
CONFIG = {
|
|
"prefixes" : [
|
|
"loop",
|
|
"loop_edge1",
|
|
"loop_edge2",
|
|
"loop_edge3",
|
|
"loop_edge4",
|
|
"loop_edge5",
|
|
"right_line",
|
|
"right_line_edge1",
|
|
"right_line_edge2",
|
|
"right_line_edge3",
|
|
]
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.setup_needed_patterns()
|
|
|
|
self.break_down_loop()
|
|
self.break_down_long_line()
|
|
|
|
def break_down_loop(self):
|
|
loop = self.loop
|
|
loop[0].set_color(WHITE)
|
|
edges = Group(*[
|
|
getattr(self, "loop_edge%d"%d)
|
|
for d in range(1, 6)
|
|
])
|
|
colors = color_gradient([BLUE, YELLOW, RED], 5)
|
|
for edge, color in zip(edges, colors):
|
|
for mob in edge:
|
|
mob.set_color(color)
|
|
loop.generate_target()
|
|
edges.generate_target()
|
|
for edge in edges:
|
|
edge[0].set_stroke(width = 0)
|
|
edge.save_state()
|
|
edge[1].set_opacity(0)
|
|
equation = self.get_equation(loop.target, *edges.target)
|
|
equation.set_width(FRAME_WIDTH - 1)
|
|
equation.to_edge(UP)
|
|
symbols = VGroup(*equation[1::2])
|
|
|
|
randy = Randolph()
|
|
randy.to_corner(DOWN+LEFT)
|
|
|
|
self.add(randy)
|
|
self.play(
|
|
FadeIn(loop),
|
|
randy.change, "pondering", loop
|
|
)
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, edges,
|
|
lambda e : (e.restore,),
|
|
run_time = 4
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
MoveToTarget(loop, run_time = 2),
|
|
MoveToTarget(edges, run_time = 2),
|
|
Write(symbols),
|
|
randy.change, "happy", equation,
|
|
)
|
|
self.wait()
|
|
|
|
self.loop_equation = equation
|
|
self.randy = randy
|
|
|
|
def break_down_long_line(self):
|
|
randy = self.randy
|
|
line = self.right_line
|
|
line[0].set_color(WHITE)
|
|
edges = Group(*[
|
|
getattr(self, "right_line_edge%d"%d)
|
|
for d in range(1, 4)
|
|
])
|
|
colors = Color(MAROON_B).range_to(PURPLE, 3)
|
|
for edge, color in zip(edges, colors):
|
|
for mob in edge:
|
|
mob.set_color(color)
|
|
equation = self.get_equation(line, *edges)
|
|
equation.set_height(self.loop_equation.get_height())
|
|
equation.next_to(
|
|
self.loop_equation, DOWN, MED_LARGE_BUFF, LEFT
|
|
)
|
|
image_map = get_organized_images()
|
|
digits = VGroup(*[
|
|
MNistMobject(image_map[n][1])
|
|
for n in (1, 4, 7)
|
|
])
|
|
digits.arrange(RIGHT)
|
|
digits.next_to(randy, RIGHT)
|
|
|
|
self.revert_to_original_skipping_status()
|
|
self.play(
|
|
FadeIn(line),
|
|
randy.change, "hesitant", line
|
|
)
|
|
self.play(Blink(randy))
|
|
self.play(OldLaggedStart(FadeIn, digits))
|
|
self.wait()
|
|
self.play(
|
|
OldLaggedStart(FadeIn, Group(*equation[1:])),
|
|
randy.change, "pondering", equation
|
|
)
|
|
self.wait(3)
|
|
|
|
class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer):
|
|
CONFIG = {
|
|
"camera_config" : {
|
|
"background_opacity" : 1,
|
|
},
|
|
"network_mob_config" : {
|
|
"layer_to_layer_buff" : 2,
|
|
"edge_propogation_color" : YELLOW,
|
|
}
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.setup_activations_and_nines()
|
|
|
|
self.describe_second_layer()
|
|
self.show_propogation()
|
|
self.ask_question()
|
|
|
|
def setup_network_mob(self):
|
|
self.network_mob.scale(0.7)
|
|
self.network_mob.to_edge(DOWN)
|
|
self.remove_random_edges(0.7)
|
|
|
|
def setup_activations_and_nines(self):
|
|
layers = self.network_mob.layers
|
|
nine_im, loop_im, line_im = images = [
|
|
Image.open(get_full_raster_image_path("handwritten_%s"%s))
|
|
for s in ("nine", "upper_loop", "right_line")
|
|
]
|
|
nine_array, loop_array, line_array = [
|
|
np.array(im)[:,:,0]/255.0
|
|
for im in images
|
|
]
|
|
self.nine = MNistMobject(nine_array.flatten())
|
|
self.nine.set_height(1.5)
|
|
self.nine[0].set_color(WHITE)
|
|
make_transparent(self.nine[1])
|
|
self.nine.next_to(layers[0].neurons, UP)
|
|
|
|
self.activations = self.network.get_activation_of_all_layers(
|
|
nine_array.flatten()
|
|
)
|
|
self.activations[-2] = np.array([1, 0, 1] + 13*[0])
|
|
|
|
|
|
self.edge_colored_nine = Group()
|
|
nine_pa = self.nine[1].pixel_array
|
|
n, k = 6, 4
|
|
colors = color_gradient([BLUE, YELLOW, RED, MAROON_B, GREEN], 10)
|
|
for i, j in it.product(list(range(n)), list(range(k))):
|
|
mob = ImageMobject(np.zeros((28, 28, 4), dtype = 'uint8'))
|
|
mob.replace(self.nine[1])
|
|
pa = mob.pixel_array
|
|
color = colors[(k*i + j)%(len(colors))]
|
|
rgb = (255*color_to_rgb(color)).astype('uint8')
|
|
pa[:,:,:3] = rgb
|
|
i0, i1 = 1+(28/n)*i, 1+(28/n)*(i+1)
|
|
j0, j1 = (28/k)*j, (28/k)*(j+1)
|
|
pa[i0:i1,j0:j1,3] = nine_pa[i0:i1,j0:j1,3]
|
|
self.edge_colored_nine.add(mob)
|
|
self.edge_colored_nine.next_to(layers[1], UP)
|
|
|
|
loop, line = [
|
|
ImageMobject(layer_to_image_array(array.flatten()))
|
|
for array in (loop_array, line_array)
|
|
]
|
|
for mob, color in (loop, YELLOW), (line, RED):
|
|
make_transparent(mob)
|
|
mob.set_color(color)
|
|
mob.replace(self.nine[1])
|
|
line.pixel_array[:14,:,:] = 0
|
|
|
|
self.pattern_colored_nine = Group(loop, line)
|
|
self.pattern_colored_nine.next_to(layers[2], UP)
|
|
|
|
for mob in self.edge_colored_nine, self.pattern_colored_nine:
|
|
mob.align_to(self.nine[1], UP)
|
|
|
|
def describe_second_layer(self):
|
|
layer = self.network_mob.layers[1]
|
|
rect = SurroundingRectangle(layer)
|
|
words = TextMobject("``Little edge'' layer?")
|
|
words.next_to(rect, UP, MED_LARGE_BUFF)
|
|
words.set_color(YELLOW)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(words, run_time = 2)
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [rect, words])))
|
|
|
|
def show_propogation(self):
|
|
nine = self.nine
|
|
edge_colored_nine = self.edge_colored_nine
|
|
pattern_colored_nine = self.pattern_colored_nine
|
|
activations = self.activations
|
|
network_mob = self.network_mob
|
|
layers = network_mob.layers
|
|
edge_groups = network_mob.edge_groups.copy()
|
|
edge_groups.set_stroke(YELLOW, 4)
|
|
|
|
v_nine = PixelsAsSquares(nine[1])
|
|
neurons = VGroup()
|
|
for pixel in v_nine:
|
|
neuron = Circle(
|
|
radius = pixel.get_width()/2,
|
|
stroke_color = WHITE,
|
|
stroke_width = 1,
|
|
fill_color = WHITE,
|
|
fill_opacity = pixel.get_fill_opacity(),
|
|
)
|
|
neuron.rotate(3*np.pi/4)
|
|
neuron.move_to(pixel)
|
|
neurons.add(neuron)
|
|
neurons.set_height(2)
|
|
neurons.space_out_submobjects(1.2)
|
|
neurons.next_to(network_mob, LEFT)
|
|
self.set_neurons_target(neurons, layers[0])
|
|
|
|
pattern_colored_nine.save_state()
|
|
pattern_colored_nine.move_to(edge_colored_nine)
|
|
edge_colored_nine.save_state()
|
|
edge_colored_nine.move_to(nine[1])
|
|
for mob in edge_colored_nine, pattern_colored_nine:
|
|
for submob in mob:
|
|
submob.set_opacity(0)
|
|
|
|
active_layers = [
|
|
network_mob.get_active_layer(i, a)
|
|
for i, a in enumerate(activations)
|
|
]
|
|
|
|
def activate_layer(i):
|
|
self.play(
|
|
ShowCreationThenDestruction(
|
|
edge_groups[i-1],
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
),
|
|
FadeIn(active_layers[i])
|
|
)
|
|
|
|
|
|
self.play(FadeIn(nine))
|
|
self.play(ReplacementTransform(v_nine, neurons))
|
|
self.play(MoveToTarget(
|
|
neurons,
|
|
remover = True,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
))
|
|
|
|
activate_layer(1)
|
|
self.play(edge_colored_nine.restore)
|
|
self.separate_parts(edge_colored_nine)
|
|
self.wait()
|
|
|
|
activate_layer(2)
|
|
self.play(pattern_colored_nine.restore)
|
|
self.separate_parts(pattern_colored_nine)
|
|
|
|
activate_layer(3)
|
|
self.wait(2)
|
|
|
|
def ask_question(self):
|
|
question = TextMobject(
|
|
"Does the network \\\\ actually do this?"
|
|
)
|
|
question.to_edge(LEFT)
|
|
later = TextMobject("We'll get back \\\\ to this")
|
|
later.to_corner(UP+LEFT)
|
|
later.set_color(BLUE)
|
|
arrow = Arrow(later.get_bottom(), question.get_top())
|
|
arrow.set_color(BLUE)
|
|
|
|
self.play(Write(question, run_time = 2))
|
|
self.wait()
|
|
self.play(
|
|
FadeIn(later),
|
|
GrowFromPoint(arrow, arrow.get_start())
|
|
)
|
|
self.wait()
|
|
|
|
###
|
|
|
|
def set_neurons_target(self, neurons, layer):
|
|
neurons.generate_target()
|
|
n = len(layer.neurons)/2
|
|
Transform(
|
|
VGroup(*neurons.target[:n]),
|
|
VGroup(*layer.neurons[:n]),
|
|
).update(1)
|
|
Transform(
|
|
VGroup(*neurons.target[-n:]),
|
|
VGroup(*layer.neurons[-n:]),
|
|
).update(1)
|
|
Transform(
|
|
VGroup(*neurons.target[n:-n]),
|
|
VectorizedPoint(layer.get_center())
|
|
).update(1)
|
|
|
|
def separate_parts(self, image_group):
|
|
vects = compass_directions(len(image_group), UP)
|
|
image_group.generate_target()
|
|
for im, vect in zip(image_group.target, vects):
|
|
im.shift(MED_SMALL_BUFF*vect)
|
|
self.play(MoveToTarget(
|
|
image_group,
|
|
rate_func = there_and_back,
|
|
lag_ratio = 0.5,
|
|
run_time = 2,
|
|
))
|
|
|
|
class EdgeDetection(Scene):
|
|
CONFIG = {
|
|
"camera_config" : {"background_opacity" : 1}
|
|
}
|
|
def construct(self):
|
|
lion = ImageMobject("Lion")
|
|
edges_array = get_edges(lion.pixel_array)
|
|
edges = ImageMobject(edges_array)
|
|
group = Group(lion, edges)
|
|
group.set_height(4)
|
|
group.arrange(RIGHT)
|
|
lion_copy = lion.copy()
|
|
|
|
self.play(FadeIn(lion))
|
|
self.play(lion_copy.move_to, edges)
|
|
self.play(Transform(lion_copy, edges, run_time = 3))
|
|
self.wait(2)
|
|
|
|
class ManyTasksBreakDownLikeThis(TeacherStudentsScene):
|
|
def construct(self):
|
|
audio = self.get_wave_form()
|
|
audio_label = TextMobject("Raw audio")
|
|
letters = TextMobject(" ".join("recognition"))
|
|
syllables = TextMobject("$\\cdot$".join([
|
|
"re", "cog", "ni", "tion"
|
|
]))
|
|
word = TextMobject(
|
|
"re", "cognition",
|
|
arg_separator = ""
|
|
)
|
|
word[1].set_color(BLUE)
|
|
arrows = VGroup()
|
|
def get_arrow():
|
|
arrow = Arrow(ORIGIN, RIGHT, color = BLUE)
|
|
arrows.add(arrow)
|
|
return arrow
|
|
sequence = VGroup(
|
|
audio, get_arrow(),
|
|
letters, get_arrow(),
|
|
syllables, get_arrow(),
|
|
word
|
|
)
|
|
sequence.arrange(RIGHT)
|
|
sequence.set_width(FRAME_WIDTH - 1)
|
|
sequence.to_edge(UP)
|
|
|
|
audio_label.next_to(audio, DOWN)
|
|
VGroup(audio, audio_label).set_color(YELLOW)
|
|
audio.save_state()
|
|
|
|
self.teacher_says(
|
|
"Many", "recognition", "tasks\\\\",
|
|
"break down like this"
|
|
)
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.wait()
|
|
content = self.teacher.bubble.content
|
|
pre_word = content[1]
|
|
content.remove(pre_word)
|
|
audio.move_to(pre_word)
|
|
self.play(
|
|
self.teacher.bubble.content.fade, 1,
|
|
ShowCreation(audio),
|
|
pre_word.shift, MED_SMALL_BUFF, DOWN
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
RemovePiCreatureBubble(self.teacher),
|
|
audio.restore,
|
|
FadeIn(audio_label),
|
|
*[
|
|
ReplacementTransform(
|
|
m1, m2
|
|
)
|
|
for m1, m2 in zip(pre_word, letters)
|
|
]
|
|
)
|
|
self.play(
|
|
GrowFromPoint(arrows[0], arrows[0].get_start()),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromPoint(arrows[1], arrows[1].get_start()),
|
|
OldLaggedStart(FadeIn, syllables, run_time = 1)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowFromPoint(arrows[2], arrows[2].get_start()),
|
|
OldLaggedStart(FadeIn, word, run_time = 1)
|
|
)
|
|
self.wait()
|
|
|
|
def get_wave_form(self):
|
|
func = lambda x : abs(sum([
|
|
(1./n)*np.sin((n+3)*x)
|
|
for n in range(1, 5)
|
|
]))
|
|
result = VGroup(*[
|
|
Line(func(x)*DOWN, func(x)*UP)
|
|
for x in np.arange(0, 4, 0.1)
|
|
])
|
|
result.set_stroke(width = 2)
|
|
result.arrange(RIGHT, buff = MED_SMALL_BUFF)
|
|
result.set_height(1)
|
|
|
|
return result
|
|
|
|
class AskAboutWhatEdgesAreDoing(IntroduceEachLayer):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"layer_to_layer_buff" : 2,
|
|
}
|
|
}
|
|
def construct(self):
|
|
self.add_question()
|
|
self.show_propogation()
|
|
|
|
def add_question(self):
|
|
self.network_mob.scale(0.8)
|
|
self.network_mob.to_edge(DOWN)
|
|
edge_groups = self.network_mob.edge_groups
|
|
self.remove_random_edges(0.7)
|
|
|
|
question = TextMobject(
|
|
"What are these connections actually doing?"
|
|
)
|
|
question.to_edge(UP)
|
|
question.shift(RIGHT)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
question.get_bottom(),
|
|
edge_group.get_top()
|
|
)
|
|
for edge_group in edge_groups
|
|
])
|
|
|
|
self.add(question, arrows)
|
|
|
|
def show_propogation(self):
|
|
in_vect = get_organized_images()[6][3]
|
|
image = MNistMobject(in_vect)
|
|
image.next_to(self.network_mob, LEFT, MED_SMALL_BUFF, UP)
|
|
|
|
self.add(image)
|
|
self.feed_forward(in_vect)
|
|
self.wait()
|
|
|
|
class IntroduceWeights(IntroduceEachLayer):
|
|
CONFIG = {
|
|
"weights_color" : GREEN,
|
|
"negative_weights_color" : RED,
|
|
}
|
|
def construct(self):
|
|
self.zoom_in_on_one_neuron()
|
|
self.show_desired_pixel_region()
|
|
self.ask_about_parameters()
|
|
self.show_weights()
|
|
self.show_weighted_sum()
|
|
self.organize_weights_as_grid()
|
|
self.make_most_weights_0()
|
|
self.add_negative_weights_around_the_edge()
|
|
|
|
def zoom_in_on_one_neuron(self):
|
|
self.network_mob.to_edge(LEFT)
|
|
layers = self.network_mob.layers
|
|
edge_groups = self.network_mob.edge_groups
|
|
|
|
neuron = layers[1].neurons[7].deepcopy()
|
|
|
|
self.play(
|
|
FadeOut(edge_groups),
|
|
FadeOut(VGroup(*layers[1:])),
|
|
FadeOut(self.network_mob.output_labels),
|
|
Animation(neuron),
|
|
neuron.edges_in.set_stroke, None, 2,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
)
|
|
|
|
self.neuron = neuron
|
|
|
|
def show_desired_pixel_region(self):
|
|
neuron = self.neuron
|
|
d = 28
|
|
|
|
pixels = PixelsAsSquares(ImageMobject(
|
|
np.zeros((d, d, 4))
|
|
))
|
|
pixels.set_stroke(width = 0.5)
|
|
pixels.set_fill(WHITE, 0)
|
|
pixels.set_height(4)
|
|
pixels.next_to(neuron, RIGHT, LARGE_BUFF)
|
|
rect = SurroundingRectangle(pixels, color = BLUE)
|
|
|
|
pixels_to_detect = self.get_pixels_to_detect(pixels)
|
|
|
|
self.play(
|
|
FadeIn(rect),
|
|
ShowCreation(
|
|
pixels,
|
|
lag_ratio = 0.5,
|
|
run_time = 2,
|
|
)
|
|
)
|
|
self.play(
|
|
pixels_to_detect.set_fill, WHITE, 1,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
)
|
|
self.wait(2)
|
|
|
|
self.pixels = pixels
|
|
self.pixels_to_detect = pixels_to_detect
|
|
self.pixels_group = VGroup(rect, pixels)
|
|
|
|
def ask_about_parameters(self):
|
|
pixels = self.pixels
|
|
pixels_group = self.pixels_group
|
|
neuron = self.neuron
|
|
|
|
question = TextMobject("What", "parameters", "should exist?")
|
|
parameter_word = question.get_part_by_tex("parameters")
|
|
parameter_word.set_color(self.weights_color)
|
|
question.move_to(neuron.edges_in.get_top(), LEFT)
|
|
arrow = Arrow(
|
|
parameter_word.get_bottom(),
|
|
neuron.edges_in[0].get_center(),
|
|
color = self.weights_color
|
|
)
|
|
|
|
p_labels = VGroup(*[
|
|
TexMobject("p_%d\\!:"%(i+1)).set_color(self.weights_color)
|
|
for i in range(8)
|
|
] + [TexMobject("\\vdots")])
|
|
p_labels.arrange(DOWN, aligned_edge = LEFT)
|
|
p_labels.next_to(parameter_word, DOWN, LARGE_BUFF)
|
|
p_labels[-1].shift(SMALL_BUFF*RIGHT)
|
|
|
|
def get_alpha_func(i, start = 0):
|
|
# m = int(5*np.sin(2*np.pi*i/128.))
|
|
m = random.randint(1, 10)
|
|
return lambda a : start + (1-2*start)*np.sin(np.pi*a*m)**2
|
|
|
|
decimals = VGroup()
|
|
changing_decimals = []
|
|
for i, p_label in enumerate(p_labels[:-1]):
|
|
decimal = DecimalNumber(0)
|
|
decimal.next_to(p_label, RIGHT, MED_SMALL_BUFF)
|
|
decimals.add(decimal)
|
|
changing_decimals.append(ChangingDecimal(
|
|
decimal, get_alpha_func(i + 5)
|
|
))
|
|
for i, pixel in enumerate(pixels):
|
|
pixel.func = get_alpha_func(i, pixel.get_fill_opacity())
|
|
pixel_updates = [
|
|
UpdateFromAlphaFunc(
|
|
pixel,
|
|
lambda p, a : p.set_fill(opacity = p.func(a))
|
|
)
|
|
for pixel in pixels
|
|
]
|
|
|
|
self.play(
|
|
Write(question, run_time = 2),
|
|
GrowFromPoint(arrow, arrow.get_start()),
|
|
pixels_group.set_height, 3,
|
|
pixels_group.to_edge, RIGHT,
|
|
OldLaggedStart(FadeIn, p_labels),
|
|
OldLaggedStart(FadeIn, decimals),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
*changing_decimals + pixel_updates,
|
|
run_time = 5,
|
|
rate_func=linear
|
|
)
|
|
|
|
self.question = question
|
|
self.weight_arrow = arrow
|
|
self.p_labels = p_labels
|
|
self.decimals = decimals
|
|
|
|
def show_weights(self):
|
|
p_labels = self.p_labels
|
|
decimals = self.decimals
|
|
arrow = self.weight_arrow
|
|
question = self.question
|
|
neuron = self.neuron
|
|
edges = neuron.edges_in
|
|
|
|
parameter_word = question.get_part_by_tex("parameters")
|
|
question.remove(parameter_word)
|
|
weights_word = TextMobject("Weights", "")[0]
|
|
weights_word.set_color(self.weights_color)
|
|
weights_word.move_to(parameter_word)
|
|
|
|
w_labels = VGroup()
|
|
for p_label in p_labels:
|
|
w_label = TexMobject(
|
|
p_label.get_tex_string().replace("p", "w")
|
|
)
|
|
w_label.set_color(self.weights_color)
|
|
w_label.move_to(p_label)
|
|
w_labels.add(w_label)
|
|
|
|
edges.generate_target()
|
|
random_numbers = 1.5*np.random.random(len(edges))-0.5
|
|
self.make_edges_weighted(edges.target, random_numbers)
|
|
def get_alpha_func(r):
|
|
return lambda a : (4*r)*a
|
|
|
|
self.play(
|
|
FadeOut(question),
|
|
ReplacementTransform(parameter_word, weights_word),
|
|
ReplacementTransform(p_labels, w_labels)
|
|
)
|
|
self.play(
|
|
MoveToTarget(edges),
|
|
*[
|
|
ChangingDecimal(
|
|
decimal,
|
|
get_alpha_func(r)
|
|
)
|
|
for decimal, r in zip(decimals, random_numbers)
|
|
]
|
|
)
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, edges,
|
|
lambda m : (m.rotate_in_place, np.pi/24),
|
|
rate_func = wiggle,
|
|
run_time = 2
|
|
))
|
|
self.wait()
|
|
|
|
self.w_labels = w_labels
|
|
self.weights_word = weights_word
|
|
self.random_numbers = random_numbers
|
|
|
|
def show_weighted_sum(self):
|
|
weights_word = self.weights_word
|
|
weight_arrow = self.weight_arrow
|
|
w_labels = VGroup(*[
|
|
VGroup(*label[:-1]).copy()
|
|
for label in self.w_labels
|
|
])
|
|
layer = self.network_mob.layers[0]
|
|
|
|
a_vect = np.random.random(16)
|
|
active_layer = self.network_mob.get_active_layer(0, a_vect)
|
|
|
|
a_labels = VGroup(*[
|
|
TexMobject("a_%d"%d)
|
|
for d in range(1, 5)
|
|
])
|
|
|
|
weighted_sum = VGroup(*it.chain(*[
|
|
[w, a, TexMobject("+")]
|
|
for w, a in zip(w_labels, a_labels)
|
|
]))
|
|
weighted_sum.add(
|
|
TexMobject("\\cdots"),
|
|
TexMobject("+"),
|
|
TexMobject("w_n").set_color(self.weights_color),
|
|
TexMobject("a_n")
|
|
)
|
|
weighted_sum.arrange(RIGHT, buff = SMALL_BUFF)
|
|
weighted_sum.to_edge(UP)
|
|
|
|
self.play(Transform(layer, active_layer))
|
|
self.play(
|
|
FadeOut(weights_word),
|
|
FadeOut(weight_arrow),
|
|
*[
|
|
ReplacementTransform(n.copy(), a)
|
|
for n, a in zip(layer.neurons, a_labels)
|
|
] + [
|
|
ReplacementTransform(n.copy(), weighted_sum[-4])
|
|
for n in layer.neurons[4:-1]
|
|
] + [
|
|
ReplacementTransform(
|
|
layer.neurons[-1].copy(),
|
|
weighted_sum[-1]
|
|
)
|
|
] + [
|
|
Write(weighted_sum[i])
|
|
for i in list(range(2, 12, 3)) + [-4, -3]
|
|
],
|
|
run_time = 1.5
|
|
)
|
|
self.wait()
|
|
self.play(*[
|
|
ReplacementTransform(w1.copy(), w2)
|
|
for w1, w2 in zip(self.w_labels, w_labels)[:4]
|
|
]+[
|
|
ReplacementTransform(w.copy(), weighted_sum[-4])
|
|
for w in self.w_labels[4:-1]
|
|
]+[
|
|
ReplacementTransform(
|
|
self.w_labels[-1].copy(), weighted_sum[-2]
|
|
)
|
|
], run_time = 2)
|
|
self.wait(2)
|
|
|
|
self.weighted_sum = weighted_sum
|
|
|
|
def organize_weights_as_grid(self):
|
|
pixels = self.pixels
|
|
w_labels = self.w_labels
|
|
decimals = self.decimals
|
|
|
|
weights = 2*np.sqrt(np.random.random(784))-1
|
|
weights[:8] = self.random_numbers[:8]
|
|
weights[-8:] = self.random_numbers[-8:]
|
|
|
|
weight_grid = PixelsFromVect(np.abs(weights))
|
|
weight_grid.replace(pixels)
|
|
weight_grid.next_to(pixels, LEFT)
|
|
for weight, pixel in zip(weights, weight_grid):
|
|
if weight >= 0:
|
|
color = self.weights_color
|
|
else:
|
|
color = self.negative_weights_color
|
|
pixel.set_fill(color, opacity = abs(weight))
|
|
|
|
self.play(FadeOut(w_labels))
|
|
self.play(
|
|
FadeIn(
|
|
VGroup(*weight_grid[len(decimals):]),
|
|
lag_ratio = 0.5,
|
|
run_time = 3
|
|
),
|
|
*[
|
|
ReplacementTransform(decimal, pixel)
|
|
for decimal, pixel in zip(decimals, weight_grid)
|
|
]
|
|
)
|
|
self.wait()
|
|
|
|
self.weight_grid = weight_grid
|
|
|
|
def make_most_weights_0(self):
|
|
weight_grid = self.weight_grid
|
|
pixels = self.pixels
|
|
pixels_group = self.pixels_group
|
|
|
|
weight_grid.generate_target()
|
|
for w, p in zip(weight_grid.target, pixels):
|
|
if p.get_fill_opacity() > 0.1:
|
|
w.set_fill(GREEN, 0.5)
|
|
else:
|
|
w.set_fill(BLACK, 0.5)
|
|
w.set_stroke(WHITE, 0.5)
|
|
|
|
digit = self.get_digit()
|
|
digit.replace(pixels)
|
|
|
|
self.play(MoveToTarget(
|
|
weight_grid,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
self.play(Transform(
|
|
pixels, digit,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
self.play(weight_grid.move_to, pixels)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(
|
|
self.pixels_to_detect.copy(),
|
|
self.weighted_sum,
|
|
run_time = 3,
|
|
lag_ratio = 0.5
|
|
),
|
|
Animation(weight_grid),
|
|
)
|
|
self.wait()
|
|
|
|
def add_negative_weights_around_the_edge(self):
|
|
weight_grid = self.weight_grid
|
|
pixels = self.pixels
|
|
|
|
self.play(weight_grid.next_to, pixels, LEFT)
|
|
self.play(*[
|
|
ApplyMethod(
|
|
weight_grid[28*y + x].set_fill,
|
|
self.negative_weights_color,
|
|
0.5
|
|
)
|
|
for y in (6, 10)
|
|
for x in range(14-4, 14+4)
|
|
])
|
|
self.wait(2)
|
|
self.play(weight_grid.move_to, pixels)
|
|
self.wait(2)
|
|
|
|
####
|
|
|
|
def get_digit(self):
|
|
digit_vect = get_organized_images()[7][4]
|
|
digit = PixelsFromVect(digit_vect)
|
|
digit.set_stroke(width = 0.5)
|
|
return digit
|
|
|
|
def get_pixels_to_detect(self, pixels):
|
|
d = int(np.sqrt(len(pixels)))
|
|
return VGroup(*it.chain(*[
|
|
pixels[d*n + d/2 - 4 : d*n + d/2 + 4]
|
|
for n in range(7, 10)
|
|
]))
|
|
|
|
def get_surrounding_pixels_for_edge(self, pixels):
|
|
d = int(np.sqrt(len(pixels)))
|
|
return VGroup(*it.chain(*[
|
|
pixels[d*n + d/2 - 4 : d*n + d/2 + 4]
|
|
for n in (6, 10)
|
|
]))
|
|
|
|
def make_edges_weighted(self, edges, weights):
|
|
for edge, r in zip(edges, weights):
|
|
if r > 0:
|
|
color = self.weights_color
|
|
else:
|
|
color = self.negative_weights_color
|
|
edge.set_stroke(color, 6*abs(r))
|
|
|
|
class MotivateSquishing(Scene):
|
|
def construct(self):
|
|
self.add_weighted_sum()
|
|
self.show_real_number_line()
|
|
self.show_interval()
|
|
self.squish_into_interval()
|
|
|
|
def add_weighted_sum(self):
|
|
weighted_sum = TexMobject(*it.chain(*[
|
|
["w_%d"%d, "a_%d"%d, "+"]
|
|
for d in range(1, 5)
|
|
] + [
|
|
["\\cdots", "+", "w_n", "a_n"]
|
|
]))
|
|
weighted_sum.set_color_by_tex("w_", GREEN)
|
|
weighted_sum.to_edge(UP)
|
|
self.add(weighted_sum)
|
|
self.weighted_sum = weighted_sum
|
|
|
|
def show_real_number_line(self):
|
|
weighted_sum = self.weighted_sum
|
|
number_line = NumberLine(unit_size = 1.5)
|
|
number_line.add_numbers()
|
|
number_line.shift(UP)
|
|
arrow1, arrow2 = [
|
|
Arrow(
|
|
weighted_sum.get_bottom(),
|
|
number_line.number_to_point(n),
|
|
)
|
|
for n in (-3, 3)
|
|
]
|
|
|
|
self.play(Write(number_line))
|
|
self.play(GrowFromPoint(arrow1, arrow1.get_start()))
|
|
self.play(Transform(
|
|
arrow1, arrow2,
|
|
run_time = 5,
|
|
rate_func = there_and_back
|
|
))
|
|
self.play(FadeOut(arrow1))
|
|
|
|
self.number_line = number_line
|
|
|
|
def show_interval(self):
|
|
lower_number_line = self.number_line.copy()
|
|
lower_number_line.shift(2*DOWN)
|
|
lower_number_line.set_color(LIGHT_GREY)
|
|
lower_number_line.numbers.set_color(WHITE)
|
|
interval = Line(
|
|
lower_number_line.number_to_point(0),
|
|
lower_number_line.number_to_point(1),
|
|
color = YELLOW,
|
|
stroke_width = 5
|
|
)
|
|
brace = Brace(interval, DOWN, buff = 0.7)
|
|
words = TextMobject("Activations should be in this range")
|
|
words.next_to(brace, DOWN, SMALL_BUFF)
|
|
|
|
self.play(ReplacementTransform(
|
|
self.number_line.copy(), lower_number_line
|
|
))
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
GrowFromCenter(interval),
|
|
)
|
|
self.play(Write(words, run_time = 2))
|
|
self.wait()
|
|
|
|
self.lower_number_line = lower_number_line
|
|
|
|
def squish_into_interval(self):
|
|
line = self.number_line
|
|
line.remove(*line.numbers)
|
|
ghost_line = line.copy()
|
|
ghost_line.fade(0.5)
|
|
ghost_line.set_color(BLUE_E)
|
|
self.add(ghost_line, line)
|
|
lower_line = self.lower_number_line
|
|
|
|
line.generate_target()
|
|
u = line.unit_size
|
|
line.target.apply_function(
|
|
lambda p : np.array([u*sigmoid(p[0])]+list(p[1:]))
|
|
)
|
|
line.target.move_to(lower_line.number_to_point(0.5))
|
|
|
|
arrow = Arrow(
|
|
line.numbers.get_bottom(),
|
|
line.target.get_top(),
|
|
color = YELLOW
|
|
)
|
|
|
|
self.play(
|
|
MoveToTarget(line),
|
|
GrowFromPoint(arrow, arrow.get_start())
|
|
)
|
|
self.wait(2)
|
|
|
|
class IntroduceSigmoid(GraphScene):
|
|
CONFIG = {
|
|
"x_min" : -5,
|
|
"x_max" : 5,
|
|
"x_axis_width" : 12,
|
|
"y_min" : -1,
|
|
"y_max" : 2,
|
|
"y_axis_label" : "",
|
|
"graph_origin" : DOWN,
|
|
"x_labeled_nums" : list(range(-4, 5)),
|
|
"y_labeled_nums" : list(range(-1, 3)),
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_title()
|
|
self.add_graph()
|
|
self.show_part(-5, -2, RED)
|
|
self.show_part(2, 5, GREEN)
|
|
self.show_part(-2, 2, BLUE)
|
|
|
|
def add_title(self):
|
|
name = TextMobject("Sigmoid")
|
|
name.next_to(ORIGIN, RIGHT, LARGE_BUFF)
|
|
name.to_edge(UP)
|
|
char = self.x_axis_label.replace("$", "")
|
|
equation = TexMobject(
|
|
"\\sigma(%s) = \\frac{1}{1+e^{-%s}}"%(char, char)
|
|
)
|
|
equation.next_to(name, DOWN)
|
|
self.add(equation, name)
|
|
|
|
self.equation = equation
|
|
self.sigmoid_name = name
|
|
|
|
def add_graph(self):
|
|
graph = self.get_graph(
|
|
lambda x : 1./(1+np.exp(-x)),
|
|
color = YELLOW
|
|
)
|
|
|
|
self.play(ShowCreation(graph))
|
|
self.wait()
|
|
|
|
self.sigmoid_graph = graph
|
|
|
|
###
|
|
|
|
def show_part(self, x_min, x_max, color):
|
|
line, graph_part = [
|
|
self.get_graph(
|
|
func,
|
|
x_min = x_min,
|
|
x_max = x_max,
|
|
color = color,
|
|
).set_stroke(width = 4)
|
|
for func in (lambda x : 0, sigmoid)
|
|
]
|
|
|
|
self.play(ShowCreation(line))
|
|
self.wait()
|
|
self.play(Transform(line, graph_part))
|
|
self.wait()
|
|
|
|
class IncludeBias(IntroduceWeights):
|
|
def construct(self):
|
|
self.force_skipping()
|
|
self.zoom_in_on_one_neuron()
|
|
self.setup_start()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.add_sigmoid_label()
|
|
self.words_on_activation()
|
|
self.comment_on_need_for_bias()
|
|
self.add_bias()
|
|
self.summarize_weights_and_biases()
|
|
|
|
def setup_start(self):
|
|
self.weighted_sum = self.get_weighted_sum()
|
|
digit = self.get_digit()
|
|
rect = SurroundingRectangle(digit)
|
|
d_group = VGroup(digit, rect)
|
|
d_group.set_height(3)
|
|
d_group.to_edge(RIGHT)
|
|
weight_grid = digit.copy()
|
|
weight_grid.set_fill(BLACK, 0.5)
|
|
self.get_pixels_to_detect(weight_grid).set_fill(
|
|
GREEN, 0.5
|
|
)
|
|
self.get_surrounding_pixels_for_edge(weight_grid).set_fill(
|
|
RED, 0.5
|
|
)
|
|
weight_grid.move_to(digit)
|
|
|
|
edges = self.neuron.edges_in
|
|
self.make_edges_weighted(
|
|
edges, 1.5*np.random.random(len(edges)) - 0.5
|
|
)
|
|
|
|
Transform(
|
|
self.network_mob.layers[0],
|
|
self.network_mob.get_active_layer(0, np.random.random(16))
|
|
).update(1)
|
|
|
|
self.add(self.weighted_sum, digit, weight_grid)
|
|
self.digit = digit
|
|
self.weight_grid = weight_grid
|
|
|
|
def add_sigmoid_label(self):
|
|
name = TextMobject("Sigmoid")
|
|
sigma = self.weighted_sum[0][0]
|
|
name.next_to(sigma, UP)
|
|
name.to_edge(UP, SMALL_BUFF)
|
|
|
|
arrow = Arrow(
|
|
name.get_bottom(), sigma.get_top(),
|
|
buff = SMALL_BUFF,
|
|
use_rectangular_stem = False,
|
|
max_tip_length_to_length_ratio = 0.3
|
|
)
|
|
|
|
self.play(
|
|
Write(name),
|
|
ShowCreation(arrow),
|
|
)
|
|
self.sigmoid_name = name
|
|
self.sigmoid_arrow = arrow
|
|
|
|
def words_on_activation(self):
|
|
neuron = self.neuron
|
|
weighted_sum = self.weighted_sum
|
|
|
|
activation_word = TextMobject("Activation")
|
|
activation_word.next_to(neuron, RIGHT)
|
|
arrow = Arrow(neuron, weighted_sum.get_bottom())
|
|
arrow.set_color(WHITE)
|
|
words = TextMobject("How positive is this?")
|
|
words.next_to(self.weighted_sum, UP, SMALL_BUFF)
|
|
|
|
self.play(
|
|
FadeIn(activation_word),
|
|
neuron.set_fill, WHITE, 0.8,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
ReplacementTransform(activation_word, words),
|
|
)
|
|
self.wait(2)
|
|
self.play(FadeOut(arrow))
|
|
|
|
self.how_positive_words = words
|
|
|
|
def comment_on_need_for_bias(self):
|
|
neuron = self.neuron
|
|
weight_grid = self.weight_grid
|
|
colored_pixels = VGroup(
|
|
self.get_pixels_to_detect(weight_grid),
|
|
self.get_surrounding_pixels_for_edge(weight_grid),
|
|
)
|
|
|
|
words = TextMobject(
|
|
"Only activate meaningfully \\\\ when",
|
|
"weighted sum", "$> 10$"
|
|
)
|
|
words.set_color_by_tex("weighted", GREEN)
|
|
words.next_to(neuron, RIGHT)
|
|
|
|
self.play(Write(words, run_time = 2))
|
|
self.play(ApplyMethod(
|
|
colored_pixels.shift, MED_LARGE_BUFF*UP,
|
|
rate_func = there_and_back,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
|
|
self.gt_ten = words[-1]
|
|
|
|
def add_bias(self):
|
|
bias = TexMobject("-10")
|
|
wn, rp = self.weighted_sum[-2:]
|
|
bias.next_to(wn, RIGHT, SMALL_BUFF)
|
|
bias.shift(0.02*UP)
|
|
rp.generate_target()
|
|
rp.target.next_to(bias, RIGHT, SMALL_BUFF)
|
|
|
|
rect = SurroundingRectangle(bias, buff = 0.5*SMALL_BUFF)
|
|
name = TextMobject("``bias''")
|
|
name.next_to(rect, DOWN)
|
|
VGroup(rect, name).set_color(BLUE)
|
|
|
|
self.play(
|
|
ReplacementTransform(
|
|
self.gt_ten.copy(), bias,
|
|
run_time = 2
|
|
),
|
|
MoveToTarget(rp),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(name)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.bias_name = name
|
|
|
|
def summarize_weights_and_biases(self):
|
|
weight_grid = self.weight_grid
|
|
bias_name = self.bias_name
|
|
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, weight_grid,
|
|
lambda p : (p.set_fill,
|
|
random.choice([GREEN, GREEN, RED]),
|
|
random.random()
|
|
),
|
|
rate_func = there_and_back,
|
|
lag_ratio = 0.4,
|
|
run_time = 4
|
|
))
|
|
self.wait()
|
|
self.play(Indicate(bias_name))
|
|
self.wait(2)
|
|
|
|
###
|
|
|
|
def get_weighted_sum(self):
|
|
args = ["\\sigma \\big("]
|
|
for d in range(1, 4):
|
|
args += ["w_%d"%d, "a_%d"%d, "+"]
|
|
args += ["\\cdots", "+", "w_n", "a_n"]
|
|
args += ["\\big)"]
|
|
weighted_sum = TexMobject(*args)
|
|
weighted_sum.set_color_by_tex("w_", GREEN)
|
|
weighted_sum.set_color_by_tex("\\big", YELLOW)
|
|
weighted_sum.to_edge(UP, LARGE_BUFF)
|
|
weighted_sum.shift(RIGHT)
|
|
|
|
return weighted_sum
|
|
|
|
class BiasForInactiviyWords(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Bias for inactivity")
|
|
words.set_color(BLUE)
|
|
words.set_width(FRAME_WIDTH - 1)
|
|
words.to_edge(UP)
|
|
|
|
self.play(Write(words))
|
|
self.wait(3)
|
|
|
|
class ContinualEdgeUpdate(ContinualAnimation):
|
|
CONFIG = {
|
|
"max_stroke_width" : 3,
|
|
"stroke_width_exp" : 7,
|
|
"n_cycles" : 5,
|
|
"colors" : [GREEN, GREEN, GREEN, RED],
|
|
}
|
|
def __init__(self, network_mob, **kwargs):
|
|
digest_config(self, kwargs)
|
|
n_cycles = self.n_cycles
|
|
edges = VGroup(*it.chain(*network_mob.edge_groups))
|
|
self.move_to_targets = []
|
|
for edge in edges:
|
|
edge.colors = [
|
|
random.choice(self.colors)
|
|
for x in range(n_cycles)
|
|
]
|
|
msw = self.max_stroke_width
|
|
edge.widths = [
|
|
msw*random.random()**self.stroke_width_exp
|
|
for x in range(n_cycles)
|
|
]
|
|
edge.cycle_time = 1 + random.random()
|
|
|
|
edge.generate_target()
|
|
edge.target.set_stroke(edge.colors[0], edge.widths[0])
|
|
self.move_to_targets.append(MoveToTarget(edge))
|
|
self.edges = edges
|
|
ContinualAnimation.__init__(self, edges, **kwargs)
|
|
|
|
def update_mobject(self, dt):
|
|
if self.internal_time < 1:
|
|
alpha = smooth(self.internal_time)
|
|
for move_to_target in self.move_to_targets:
|
|
move_to_target.update(alpha)
|
|
return
|
|
for edge in self.edges:
|
|
t = (self.internal_time-1)/edge.cycle_time
|
|
alpha = ((self.internal_time-1)%edge.cycle_time)/edge.cycle_time
|
|
low_n = int(t)%len(edge.colors)
|
|
high_n = int(t+1)%len(edge.colors)
|
|
color = interpolate_color(edge.colors[low_n], edge.colors[high_n], alpha)
|
|
width = interpolate(edge.widths[low_n], edge.widths[high_n], alpha)
|
|
edge.set_stroke(color, width)
|
|
|
|
class ShowRemainingNetwork(IntroduceWeights):
|
|
def construct(self):
|
|
self.force_skipping()
|
|
self.zoom_in_on_one_neuron()
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.show_all_of_second_layer()
|
|
self.count_in_biases()
|
|
self.compute_layer_two_of_weights_and_biases_count()
|
|
self.show_remaining_layers()
|
|
self.show_final_number()
|
|
self.tweak_weights()
|
|
|
|
def show_all_of_second_layer(self):
|
|
example_neuron = self.neuron
|
|
layer = self.network_mob.layers[1]
|
|
|
|
neurons = VGroup(*layer.neurons)
|
|
neurons.remove(example_neuron)
|
|
|
|
words = TextMobject("784", "weights", "per neuron")
|
|
words.next_to(layer.neurons[0], RIGHT)
|
|
words.to_edge(UP)
|
|
|
|
self.play(FadeIn(words))
|
|
last_edges = None
|
|
for neuron in neurons[:7]:
|
|
edges = neuron.edges_in
|
|
added_anims = []
|
|
if last_edges is not None:
|
|
added_anims += [
|
|
last_edges.set_stroke, None, 1
|
|
]
|
|
edges.set_stroke(width = 2)
|
|
self.play(
|
|
ShowCreation(edges, lag_ratio = 0.5),
|
|
FadeIn(neuron),
|
|
*added_anims,
|
|
run_time = 1.5
|
|
)
|
|
last_edges = edges
|
|
self.play(
|
|
OldLaggedStart(
|
|
ShowCreation, VGroup(*[
|
|
n.edges_in for n in neurons[7:]
|
|
]),
|
|
run_time = 3,
|
|
),
|
|
OldLaggedStart(
|
|
FadeIn, VGroup(*neurons[7:]),
|
|
run_time = 3,
|
|
),
|
|
VGroup(*last_edges[1:]).set_stroke, None, 1
|
|
)
|
|
self.wait()
|
|
|
|
self.weights_words = words
|
|
|
|
def count_in_biases(self):
|
|
neurons = self.network_mob.layers[1].neurons
|
|
words = TextMobject("One", "bias","for each")
|
|
words.next_to(neurons, RIGHT, buff = 2)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
words.get_left(),
|
|
neuron.get_center(),
|
|
color = BLUE
|
|
)
|
|
for neuron in neurons
|
|
])
|
|
|
|
self.play(
|
|
FadeIn(words),
|
|
OldLaggedStart(
|
|
GrowArrow, arrows,
|
|
run_time = 3,
|
|
lag_ratio = 0.3,
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
self.bias_words = words
|
|
self.bias_arrows = arrows
|
|
|
|
def compute_layer_two_of_weights_and_biases_count(self):
|
|
ww1, ww2, ww3 = weights_words = self.weights_words
|
|
bb1, bb2, bb3 = bias_words = self.bias_words
|
|
bias_arrows = self.bias_arrows
|
|
|
|
times_16 = TexMobject("\\times 16")
|
|
times_16.next_to(ww1, RIGHT, SMALL_BUFF)
|
|
ww2.generate_target()
|
|
ww2.target.next_to(times_16, RIGHT)
|
|
|
|
bias_count = TextMobject("16", "biases")
|
|
bias_count.next_to(ww2.target, RIGHT, LARGE_BUFF)
|
|
|
|
self.play(
|
|
Write(times_16),
|
|
MoveToTarget(ww2),
|
|
FadeOut(ww3)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(times_16.copy(), bias_count[0]),
|
|
FadeOut(bb1),
|
|
ReplacementTransform(bb2, bias_count[1]),
|
|
FadeOut(bb3),
|
|
OldLaggedStart(FadeOut, bias_arrows)
|
|
)
|
|
self.wait()
|
|
|
|
self.weights_count = VGroup(ww1, times_16, ww2)
|
|
self.bias_count = bias_count
|
|
|
|
def show_remaining_layers(self):
|
|
weights_count = self.weights_count
|
|
bias_count = self.bias_count
|
|
for count in weights_count, bias_count:
|
|
count.generate_target()
|
|
count.prefix = VGroup(*count.target[:-1])
|
|
|
|
added_weights = TexMobject(
|
|
"+16\\!\\times\\! 16 + 16 \\!\\times\\! 10"
|
|
)
|
|
added_weights.to_corner(UP+RIGHT)
|
|
weights_count.prefix.next_to(added_weights, LEFT, SMALL_BUFF)
|
|
weights_count.target[-1].next_to(
|
|
VGroup(weights_count.prefix, added_weights),
|
|
DOWN
|
|
)
|
|
|
|
added_biases = TexMobject("+ 16 + 10")
|
|
group = VGroup(bias_count.prefix, added_biases)
|
|
group.arrange(RIGHT, SMALL_BUFF)
|
|
group.next_to(weights_count.target[-1], DOWN, LARGE_BUFF)
|
|
bias_count.target[-1].next_to(group, DOWN)
|
|
|
|
network_mob = self.network_mob
|
|
edges = VGroup(*it.chain(*network_mob.edge_groups[1:]))
|
|
neurons = VGroup(*it.chain(*[
|
|
layer.neurons for layer in network_mob.layers[2:]
|
|
]))
|
|
|
|
self.play(
|
|
MoveToTarget(weights_count),
|
|
MoveToTarget(bias_count),
|
|
Write(added_weights, run_time = 1),
|
|
Write(added_biases, run_time = 1),
|
|
OldLaggedStart(
|
|
ShowCreation, edges,
|
|
run_time = 4,
|
|
lag_ratio = 0.3,
|
|
),
|
|
OldLaggedStart(
|
|
FadeIn, neurons,
|
|
run_time = 4,
|
|
lag_ratio = 0.3,
|
|
)
|
|
)
|
|
self.wait(2)
|
|
|
|
weights_count.add(added_weights)
|
|
bias_count.add(added_biases)
|
|
|
|
def show_final_number(self):
|
|
group = VGroup(
|
|
self.weights_count,
|
|
self.bias_count,
|
|
)
|
|
group.generate_target()
|
|
group.target.scale_in_place(0.8)
|
|
rect = SurroundingRectangle(group.target, buff = MED_SMALL_BUFF)
|
|
num_mob = TexMobject("13{,}002")
|
|
num_mob.scale(1.5)
|
|
num_mob.next_to(rect, DOWN)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
MoveToTarget(group),
|
|
)
|
|
self.play(Write(num_mob))
|
|
self.wait()
|
|
|
|
self.final_number = num_mob
|
|
|
|
def tweak_weights(self):
|
|
learning = TextMobject("Learning $\\rightarrow$")
|
|
finding_words = TextMobject(
|
|
"Finding the right \\\\ weights and biases"
|
|
)
|
|
group = VGroup(learning, finding_words)
|
|
group.arrange(RIGHT)
|
|
group.scale(0.8)
|
|
group.next_to(self.final_number, DOWN, MED_LARGE_BUFF)
|
|
|
|
self.add(ContinualEdgeUpdate(self.network_mob))
|
|
self.wait(5)
|
|
self.play(Write(group))
|
|
self.wait(10)
|
|
|
|
###
|
|
|
|
def get_edge_weight_wandering_anim(self, edges):
|
|
for edge in edges:
|
|
edge.generate_target()
|
|
edge.target.set_stroke(
|
|
color = random.choice([GREEN, GREEN, GREEN, RED]),
|
|
width = 3*random.random()**7
|
|
)
|
|
self.play(
|
|
OldLaggedStart(
|
|
MoveToTarget, edges,
|
|
lag_ratio = 0.6,
|
|
run_time = 2,
|
|
),
|
|
*added_anims
|
|
)
|
|
|
|
class ImagineSettingByHand(Scene):
|
|
def construct(self):
|
|
randy = Randolph()
|
|
randy.scale(0.7)
|
|
randy.to_corner(DOWN+LEFT)
|
|
|
|
bubble = randy.get_bubble()
|
|
network_mob = NetworkMobject(
|
|
Network(sizes = [8, 6, 6, 4]),
|
|
neuron_stroke_color = WHITE
|
|
)
|
|
network_mob.scale(0.7)
|
|
network_mob.move_to(bubble.get_bubble_center())
|
|
network_mob.shift(MED_SMALL_BUFF*RIGHT + SMALL_BUFF*(UP+RIGHT))
|
|
|
|
self.add(randy, bubble, network_mob)
|
|
self.add(ContinualEdgeUpdate(network_mob))
|
|
self.play(randy.change, "pondering")
|
|
self.wait()
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
self.play(randy.change, "horrified", network_mob)
|
|
self.play(Blink(randy))
|
|
self.wait(10)
|
|
|
|
class WhenTheNetworkFails(MoreHonestMNistNetworkPreview):
|
|
CONFIG = {
|
|
"network_mob_config" : {"layer_to_layer_buff" : 2}
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.black_box()
|
|
self.incorrect_classification()
|
|
self.ask_about_weights()
|
|
|
|
def setup_network_mob(self):
|
|
self.network_mob.scale(0.8)
|
|
self.network_mob.to_edge(DOWN)
|
|
|
|
def black_box(self):
|
|
network_mob = self.network_mob
|
|
layers = VGroup(*network_mob.layers[1:3])
|
|
box = SurroundingRectangle(
|
|
layers,
|
|
stroke_color = WHITE,
|
|
fill_color = BLACK,
|
|
fill_opacity = 0.8,
|
|
)
|
|
words = TextMobject("...rather than treating this as a black box")
|
|
words.next_to(box, UP, LARGE_BUFF)
|
|
|
|
self.play(
|
|
Write(words, run_time = 2),
|
|
DrawBorderThenFill(box)
|
|
)
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [words, box])))
|
|
|
|
def incorrect_classification(self):
|
|
network = self.network
|
|
training_data, validation_data, test_data = load_data_wrapper()
|
|
for in_vect, result in test_data[20:]:
|
|
network_answer = np.argmax(network.feedforward(in_vect))
|
|
if network_answer != result:
|
|
break
|
|
self.feed_in_image(in_vect)
|
|
|
|
wrong = TextMobject("Wrong!")
|
|
wrong.set_color(RED)
|
|
wrong.next_to(self.network_mob.layers[-1], UP+RIGHT)
|
|
self.play(Write(wrong, run_time = 1))
|
|
|
|
def ask_about_weights(self):
|
|
question = TextMobject(
|
|
"What weights are used here?\\\\",
|
|
"What are they doing?"
|
|
)
|
|
question.next_to(self.network_mob, UP)
|
|
|
|
self.add(ContinualEdgeUpdate(self.network_mob))
|
|
self.play(Write(question))
|
|
self.wait(10)
|
|
|
|
|
|
###
|
|
|
|
def reset_display(self, *args):
|
|
pass
|
|
|
|
class EvenWhenItWorks(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher_says(
|
|
"Even when it works,\\\\",
|
|
"dig into why."
|
|
)
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.wait(7)
|
|
|
|
class IntroduceWeightMatrix(NetworkScene):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"neuron_stroke_color" : WHITE,
|
|
"neuron_fill_color" : WHITE,
|
|
"neuron_radius" : 0.35,
|
|
"layer_to_layer_buff" : 2,
|
|
},
|
|
"layer_sizes" : [8, 6],
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.show_weighted_sum()
|
|
self.organize_activations_into_column()
|
|
self.organize_weights_as_matrix()
|
|
self.show_meaning_of_matrix_row()
|
|
self.connect_weighted_sum_to_matrix_multiplication()
|
|
self.add_bias_vector()
|
|
self.apply_sigmoid()
|
|
self.write_clean_final_expression()
|
|
|
|
def setup_network_mob(self):
|
|
self.network_mob.to_edge(LEFT, buff = LARGE_BUFF)
|
|
self.network_mob.layers[1].neurons.shift(0.02*RIGHT)
|
|
|
|
def show_weighted_sum(self):
|
|
self.fade_many_neurons()
|
|
self.activate_first_layer()
|
|
self.show_first_neuron_weighted_sum()
|
|
self.add_bias()
|
|
self.add_sigmoid()
|
|
##
|
|
|
|
def fade_many_neurons(self):
|
|
anims = []
|
|
neurons = self.network_mob.layers[1].neurons
|
|
for neuron in neurons[1:]:
|
|
neuron.save_state()
|
|
neuron.edges_in.save_state()
|
|
anims += [
|
|
neuron.fade, 0.8,
|
|
neuron.set_fill, None, 0,
|
|
neuron.edges_in.fade, 0.8,
|
|
]
|
|
anims += [
|
|
Animation(neurons[0]),
|
|
Animation(neurons[0].edges_in),
|
|
]
|
|
self.play(*anims)
|
|
|
|
def activate_first_layer(self):
|
|
layer = self.network_mob.layers[0]
|
|
activations = 0.7*np.random.random(len(layer.neurons))
|
|
active_layer = self.network_mob.get_active_layer(0, activations)
|
|
a_labels = VGroup(*[
|
|
TexMobject("a^{(0)}_%d"%d)
|
|
for d in range(len(layer.neurons))
|
|
])
|
|
for label, neuron in zip(a_labels, layer.neurons):
|
|
label.scale(0.75)
|
|
label.move_to(neuron)
|
|
|
|
self.play(
|
|
Transform(layer, active_layer),
|
|
Write(a_labels, run_time = 2)
|
|
)
|
|
|
|
self.a_labels = a_labels
|
|
|
|
def show_first_neuron_weighted_sum(self):
|
|
neuron = self.network_mob.layers[1].neurons[0]
|
|
a_labels = VGroup(*self.a_labels[:2]).copy()
|
|
a_labels.generate_target()
|
|
w_labels = VGroup(*[
|
|
TexMobject("w_{0, %d}"%d)
|
|
for d in range(len(a_labels))
|
|
])
|
|
weighted_sum = VGroup()
|
|
symbols = VGroup()
|
|
for a_label, w_label in zip(a_labels.target, w_labels):
|
|
a_label.scale(1./0.75)
|
|
plus = TexMobject("+")
|
|
weighted_sum.add(w_label, a_label, plus)
|
|
symbols.add(plus)
|
|
weighted_sum.add(
|
|
TexMobject("\\cdots"),
|
|
TexMobject("+"),
|
|
TexMobject("w_{0, n}"),
|
|
TexMobject("a^{(0)}_n"),
|
|
)
|
|
|
|
weighted_sum.arrange(RIGHT)
|
|
a1_label = TexMobject("a^{(1)}_0")
|
|
a1_label.next_to(neuron, RIGHT)
|
|
equals = TexMobject("=").next_to(a1_label, RIGHT)
|
|
weighted_sum.next_to(equals, RIGHT)
|
|
|
|
symbols.add(*weighted_sum[-4:-2])
|
|
w_labels.add(weighted_sum[-2])
|
|
a_labels.add(self.a_labels[-1].copy())
|
|
a_labels.target.add(weighted_sum[-1])
|
|
a_labels.add(VGroup(*self.a_labels[2:-1]).copy())
|
|
a_labels.target.add(VectorizedPoint(weighted_sum[-4].get_center()))
|
|
|
|
VGroup(a1_label, equals, weighted_sum).scale(
|
|
0.75, about_point = a1_label.get_left()
|
|
)
|
|
|
|
w_labels.set_color(GREEN)
|
|
w_labels.shift(0.6*SMALL_BUFF*DOWN)
|
|
a_labels.target.shift(0.5*SMALL_BUFF*UP)
|
|
|
|
self.play(
|
|
Write(a1_label),
|
|
Write(equals),
|
|
neuron.set_fill, None, 0.3,
|
|
run_time = 1
|
|
)
|
|
self.play(MoveToTarget(a_labels, run_time = 1.5))
|
|
self.play(
|
|
Write(w_labels),
|
|
Write(symbols),
|
|
)
|
|
|
|
self.a1_label = a1_label
|
|
self.a1_equals = equals
|
|
self.w_labels = w_labels
|
|
self.a_labels_in_sum = a_labels
|
|
self.symbols = symbols
|
|
self.weighted_sum = VGroup(w_labels, a_labels, symbols)
|
|
|
|
def add_bias(self):
|
|
weighted_sum = self.weighted_sum
|
|
bias = TexMobject("+\\,", "b_0")
|
|
bias.scale(0.75)
|
|
bias.next_to(weighted_sum, RIGHT, SMALL_BUFF)
|
|
bias.shift(0.5*SMALL_BUFF*DOWN)
|
|
name = TextMobject("Bias")
|
|
name.scale(0.75)
|
|
name.next_to(bias, DOWN, MED_LARGE_BUFF)
|
|
arrow = Arrow(name, bias, buff = SMALL_BUFF)
|
|
VGroup(name, arrow, bias).set_color(BLUE)
|
|
|
|
self.play(
|
|
FadeIn(name),
|
|
FadeIn(bias),
|
|
GrowArrow(arrow),
|
|
)
|
|
|
|
self.weighted_sum.add(bias)
|
|
|
|
self.bias = bias
|
|
self.bias_name = VGroup(name, arrow)
|
|
|
|
def add_sigmoid(self):
|
|
weighted_sum = self.weighted_sum
|
|
weighted_sum.generate_target()
|
|
sigma, lp, rp = mob = TexMobject("\\sigma\\big(\\big)")
|
|
# mob.scale(0.75)
|
|
sigma.move_to(weighted_sum.get_left())
|
|
sigma.shift(0.5*SMALL_BUFF*(DOWN+RIGHT))
|
|
lp.next_to(sigma, RIGHT, SMALL_BUFF)
|
|
weighted_sum.target.next_to(lp, RIGHT, SMALL_BUFF)
|
|
rp.next_to(weighted_sum.target, RIGHT, SMALL_BUFF)
|
|
|
|
name = TextMobject("Sigmoid")
|
|
name.next_to(sigma, UP, MED_LARGE_BUFF)
|
|
arrow = Arrow(name, sigma, buff = SMALL_BUFF)
|
|
sigmoid_name = VGroup(name, arrow)
|
|
VGroup(sigmoid_name, mob).set_color(YELLOW)
|
|
|
|
self.play(
|
|
FadeIn(mob),
|
|
MoveToTarget(weighted_sum),
|
|
MaintainPositionRelativeTo(self.bias_name, self.bias),
|
|
)
|
|
self.play(FadeIn(sigmoid_name))
|
|
|
|
self.sigma = sigma
|
|
self.sigma_parens = VGroup(lp, rp)
|
|
self.sigmoid_name = sigmoid_name
|
|
|
|
##
|
|
|
|
def organize_activations_into_column(self):
|
|
a_labels = self.a_labels.copy()
|
|
a_labels.generate_target()
|
|
column = a_labels.target
|
|
a_labels_in_sum = self.a_labels_in_sum
|
|
|
|
dots = TexMobject("\\vdots")
|
|
mid_as = VGroup(*column[2:-1])
|
|
Transform(mid_as, dots).update(1)
|
|
last_a = column[-1]
|
|
new_last_a = TexMobject(
|
|
last_a.get_tex_string().replace("7", "n")
|
|
)
|
|
new_last_a.replace(last_a)
|
|
Transform(last_a, new_last_a).update(1)
|
|
|
|
VGroup(
|
|
*column[:2] + [mid_as] + [column[-1]]
|
|
).arrange(DOWN)
|
|
column.shift(DOWN + 3.5*RIGHT)
|
|
|
|
pre_brackets = self.get_brackets(a_labels)
|
|
post_bracketes = self.get_brackets(column)
|
|
pre_brackets.set_fill(opacity = 0)
|
|
|
|
self.play(FocusOn(self.a_labels[0]))
|
|
self.play(OldLaggedStart(
|
|
Indicate, self.a_labels,
|
|
rate_func = there_and_back,
|
|
run_time = 1
|
|
))
|
|
self.play(
|
|
MoveToTarget(a_labels),
|
|
Transform(pre_brackets, post_bracketes),
|
|
run_time = 2
|
|
)
|
|
self.wait()
|
|
self.play(*[
|
|
OldLaggedStart(Indicate, mob, rate_func = there_and_back)
|
|
for mob in (a_labels, a_labels_in_sum)
|
|
])
|
|
self.wait()
|
|
|
|
self.a_column = a_labels
|
|
self.a_column_brackets = pre_brackets
|
|
|
|
def organize_weights_as_matrix(self):
|
|
a_column = self.a_column
|
|
a_column_brackets = self.a_column_brackets
|
|
w_brackets = a_column_brackets.copy()
|
|
w_brackets.next_to(a_column_brackets, LEFT, SMALL_BUFF)
|
|
lwb, rwb = w_brackets
|
|
|
|
w_labels = self.w_labels.copy()
|
|
w_labels.submobjects.insert(
|
|
2, self.symbols[-2].copy()
|
|
)
|
|
w_labels.generate_target()
|
|
w_labels.target.arrange(RIGHT)
|
|
w_labels.target.next_to(a_column[0], LEFT, buff = 0.8)
|
|
lwb.next_to(w_labels.target, LEFT, SMALL_BUFF)
|
|
lwb.align_to(rwb, UP)
|
|
|
|
row_1, row_k = [
|
|
VGroup(*list(map(TexMobject, [
|
|
"w_{%s, 0}"%i,
|
|
"w_{%s, 1}"%i,
|
|
"\\cdots",
|
|
"w_{%s, n}"%i,
|
|
])))
|
|
for i in ("1", "k")
|
|
]
|
|
dots_row = VGroup(*list(map(TexMobject, [
|
|
"\\vdots", "\\vdots", "\\ddots", "\\vdots"
|
|
])))
|
|
|
|
lower_rows = VGroup(row_1, dots_row, row_k)
|
|
lower_rows.scale(0.75)
|
|
last_row = w_labels.target
|
|
for row in lower_rows:
|
|
for target, mover in zip(last_row, row):
|
|
mover.move_to(target)
|
|
if "w" in mover.get_tex_string():
|
|
mover.set_color(GREEN)
|
|
row.next_to(last_row, DOWN, buff = 0.45)
|
|
last_row = row
|
|
|
|
self.play(
|
|
MoveToTarget(w_labels),
|
|
Write(w_brackets, run_time = 1)
|
|
)
|
|
self.play(FadeIn(
|
|
lower_rows,
|
|
run_time = 3,
|
|
lag_ratio = 0.5,
|
|
))
|
|
self.wait()
|
|
|
|
self.top_matrix_row = w_labels
|
|
self.lower_matrix_rows = lower_rows
|
|
self.matrix_brackets = w_brackets
|
|
|
|
def show_meaning_of_matrix_row(self):
|
|
row = self.top_matrix_row
|
|
edges = self.network_mob.layers[1].neurons[0].edges_in.copy()
|
|
edges.set_stroke(GREEN, 5)
|
|
rect = SurroundingRectangle(row, color = GREEN_B)
|
|
|
|
self.play(ShowCreation(rect))
|
|
for x in range(2):
|
|
self.play(OldLaggedStart(
|
|
ShowCreationThenDestruction, edges,
|
|
lag_ratio = 0.8
|
|
))
|
|
self.wait()
|
|
|
|
self.top_row_rect = rect
|
|
|
|
def connect_weighted_sum_to_matrix_multiplication(self):
|
|
a_column = self.a_column
|
|
a_brackets = self.a_column_brackets
|
|
top_row_rect = self.top_row_rect
|
|
|
|
column_rect = SurroundingRectangle(a_column)
|
|
|
|
equals = TexMobject("=")
|
|
equals.next_to(a_brackets, RIGHT)
|
|
result_brackets = a_brackets.copy()
|
|
result_terms = VGroup()
|
|
for i in 0, 1, 4, -1:
|
|
a = a_column[i]
|
|
if i == 4:
|
|
mob = TexMobject("\\vdots")
|
|
else:
|
|
# mob = Circle(radius = 0.2, color = YELLOW)
|
|
mob = TexMobject("?").scale(1.3).set_color(YELLOW)
|
|
result_terms.add(mob.move_to(a))
|
|
VGroup(result_brackets, result_terms).next_to(equals, RIGHT)
|
|
|
|
brace = Brace(
|
|
VGroup(self.w_labels, self.a_labels_in_sum), DOWN
|
|
)
|
|
arrow = Arrow(
|
|
brace.get_bottom(),
|
|
result_terms[0].get_top(),
|
|
buff = SMALL_BUFF
|
|
)
|
|
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
GrowFromCenter(brace),
|
|
)
|
|
self.play(
|
|
Write(equals),
|
|
FadeIn(result_brackets),
|
|
)
|
|
self.play(ShowCreation(column_rect))
|
|
self.play(ReplacementTransform(
|
|
VGroup(top_row_rect, column_rect).copy(),
|
|
result_terms[0]
|
|
))
|
|
self.play(OldLaggedStart(
|
|
FadeIn, VGroup(*result_terms[1:])
|
|
))
|
|
self.wait(2)
|
|
self.show_meaning_of_lower_rows(
|
|
arrow, brace, top_row_rect, result_terms
|
|
)
|
|
self.play(*list(map(FadeOut, [
|
|
result_terms, result_brackets, equals, column_rect
|
|
])))
|
|
|
|
def show_meaning_of_lower_rows(self, arrow, brace, row_rect, result_terms):
|
|
n1, n2, nk = neurons = VGroup(*[
|
|
self.network_mob.layers[1].neurons[i]
|
|
for i in (0, 1, -1)
|
|
])
|
|
for n in neurons:
|
|
n.save_state()
|
|
n.edges_in.save_state()
|
|
|
|
rect2 = SurroundingRectangle(result_terms[1])
|
|
rectk = SurroundingRectangle(result_terms[-1])
|
|
VGroup(rect2, rectk).set_color(WHITE)
|
|
row2 = self.lower_matrix_rows[0]
|
|
rowk = self.lower_matrix_rows[-1]
|
|
|
|
def show_edges(neuron):
|
|
self.play(OldLaggedStart(
|
|
ShowCreationThenDestruction,
|
|
neuron.edges_in.copy().set_stroke(GREEN, 5),
|
|
lag_ratio = 0.7,
|
|
run_time = 1,
|
|
))
|
|
|
|
self.play(
|
|
row_rect.move_to, row2,
|
|
n1.fade,
|
|
n1.set_fill, None, 0,
|
|
n1.edges_in.set_stroke, None, 1,
|
|
n2.set_stroke, WHITE, 3,
|
|
n2.edges_in.set_stroke, None, 3,
|
|
ReplacementTransform(arrow, rect2),
|
|
FadeOut(brace),
|
|
)
|
|
show_edges(n2)
|
|
self.play(
|
|
row_rect.move_to, rowk,
|
|
n2.restore,
|
|
n2.edges_in.restore,
|
|
nk.set_stroke, WHITE, 3,
|
|
nk.edges_in.set_stroke, None, 3,
|
|
ReplacementTransform(rect2, rectk),
|
|
)
|
|
show_edges(nk)
|
|
self.play(
|
|
n1.restore,
|
|
n1.edges_in.restore,
|
|
nk.restore,
|
|
nk.edges_in.restore,
|
|
FadeOut(rectk),
|
|
FadeOut(row_rect),
|
|
)
|
|
|
|
def add_bias_vector(self):
|
|
bias = self.bias
|
|
bias_name = self.bias_name
|
|
a_column_brackets = self.a_column_brackets
|
|
a_column = self.a_column
|
|
|
|
plus = TexMobject("+")
|
|
b_brackets = a_column_brackets.copy()
|
|
b_column = VGroup(*list(map(TexMobject, [
|
|
"b_0", "b_1", "\\vdots", "b_n",
|
|
])))
|
|
b_column.scale(0.85)
|
|
b_column.arrange(DOWN, buff = 0.35)
|
|
b_column.move_to(a_column)
|
|
b_column.set_color(BLUE)
|
|
plus.next_to(a_column_brackets, RIGHT)
|
|
VGroup(b_brackets, b_column).next_to(plus, RIGHT)
|
|
|
|
bias_rect = SurroundingRectangle(bias)
|
|
|
|
self.play(ShowCreation(bias_rect))
|
|
self.play(FadeOut(bias_rect))
|
|
self.play(
|
|
Write(plus),
|
|
Write(b_brackets),
|
|
Transform(self.bias[1].copy(), b_column[0]),
|
|
run_time = 1
|
|
)
|
|
self.play(OldLaggedStart(
|
|
FadeIn, VGroup(*b_column[1:])
|
|
))
|
|
self.wait()
|
|
|
|
self.bias_plus = plus
|
|
self.b_brackets = b_brackets
|
|
self.b_column = b_column
|
|
|
|
def apply_sigmoid(self):
|
|
expression_bounds = VGroup(
|
|
self.matrix_brackets[0], self.b_brackets[1]
|
|
)
|
|
sigma = self.sigma.copy()
|
|
slp, srp = self.sigma_parens.copy()
|
|
|
|
big_lp, big_rp = parens = TexMobject("()")
|
|
parens.scale(3)
|
|
parens.stretch_to_fit_height(expression_bounds.get_height())
|
|
big_lp.next_to(expression_bounds, LEFT, SMALL_BUFF)
|
|
big_rp.next_to(expression_bounds, RIGHT, SMALL_BUFF)
|
|
parens.set_color(YELLOW)
|
|
|
|
self.play(
|
|
sigma.scale, 2,
|
|
sigma.next_to, big_lp, LEFT, SMALL_BUFF,
|
|
Transform(slp, big_lp),
|
|
Transform(srp, big_rp),
|
|
)
|
|
self.wait(2)
|
|
|
|
self.big_sigma_group = VGroup(VGroup(sigma), slp, srp)
|
|
|
|
def write_clean_final_expression(self):
|
|
self.fade_weighted_sum()
|
|
expression = TexMobject(
|
|
"\\textbf{a}^{(1)}",
|
|
"=",
|
|
"\\sigma",
|
|
"\\big(",
|
|
"\\textbf{W}",
|
|
"\\textbf{a}^{(0)}",
|
|
"+",
|
|
"\\textbf{b}",
|
|
"\\big)",
|
|
)
|
|
expression.set_color_by_tex_to_color_map({
|
|
"sigma" : YELLOW,
|
|
"big" : YELLOW,
|
|
"W" : GREEN,
|
|
"\\textbf{b}" : BLUE
|
|
})
|
|
expression.next_to(self.big_sigma_group, UP, LARGE_BUFF)
|
|
a1, equals, sigma, lp, W, a0, plus, b, rp = expression
|
|
|
|
neuron_anims = []
|
|
neurons = VGroup(*self.network_mob.layers[1].neurons[1:])
|
|
for neuron in neurons:
|
|
neuron_anims += [
|
|
neuron.restore,
|
|
neuron.set_fill, None, random.random()
|
|
]
|
|
neuron_anims += [
|
|
neuron.edges_in.restore
|
|
]
|
|
neurons.add_to_back(self.network_mob.layers[1].neurons[0])
|
|
|
|
self.play(ReplacementTransform(
|
|
VGroup(
|
|
self.top_matrix_row, self.lower_matrix_rows,
|
|
self.matrix_brackets
|
|
).copy(),
|
|
VGroup(W),
|
|
))
|
|
self.play(ReplacementTransform(
|
|
VGroup(self.a_column, self.a_column_brackets).copy(),
|
|
VGroup(VGroup(a0)),
|
|
))
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(self.b_column, self.b_brackets).copy(),
|
|
VGroup(VGroup(b))
|
|
),
|
|
ReplacementTransform(
|
|
self.bias_plus.copy(), plus
|
|
)
|
|
)
|
|
self.play(ReplacementTransform(
|
|
self.big_sigma_group.copy(),
|
|
VGroup(sigma, lp, rp)
|
|
))
|
|
self.wait()
|
|
self.play(*neuron_anims, run_time = 2)
|
|
self.play(
|
|
ReplacementTransform(neurons.copy(), a1),
|
|
FadeIn(equals)
|
|
)
|
|
self.wait(2)
|
|
|
|
def fade_weighted_sum(self):
|
|
self.play(*list(map(FadeOut, [
|
|
self.a1_label, self.a1_equals,
|
|
self.sigma, self.sigma_parens,
|
|
self.weighted_sum,
|
|
self.bias_name,
|
|
self.sigmoid_name,
|
|
])))
|
|
|
|
|
|
###
|
|
|
|
def get_brackets(self, mob):
|
|
lb, rb = both = TexMobject("\\big[\\big]")
|
|
both.set_width(mob.get_width())
|
|
both.stretch_to_fit_height(1.2*mob.get_height())
|
|
lb.next_to(mob, LEFT, SMALL_BUFF)
|
|
rb.next_to(mob, RIGHT, SMALL_BUFF)
|
|
return both
|
|
|
|
class HorrifiedMorty(Scene):
|
|
def construct(self):
|
|
morty = Mortimer()
|
|
morty.flip()
|
|
morty.scale(2)
|
|
|
|
for mode in "horrified", "hesitant":
|
|
self.play(
|
|
morty.change, mode,
|
|
morty.look, UP,
|
|
)
|
|
self.play(Blink(morty))
|
|
self.wait(2)
|
|
|
|
class SigmoidAppliedToVector(Scene):
|
|
def construct(self):
|
|
tex = TexMobject("""
|
|
\\sigma \\left(
|
|
\\left[\\begin{array}{c}
|
|
x \\\\ y \\\\ z
|
|
\\end{array}\\right]
|
|
\\right) =
|
|
\\left[\\begin{array}{c}
|
|
\\sigma(x) \\\\ \\sigma(y) \\\\ \\sigma(z)
|
|
\\end{array}\\right]
|
|
""")
|
|
tex.set_width(FRAME_WIDTH - 1)
|
|
tex.to_edge(DOWN)
|
|
indices = it.chain(
|
|
[0], list(range(1, 5)), list(range(16, 16+4)),
|
|
list(range(25, 25+2)), [25+3],
|
|
list(range(29, 29+2)), [29+3],
|
|
list(range(33, 33+2)), [33+3],
|
|
)
|
|
for i in indices:
|
|
tex[i].set_color(YELLOW)
|
|
self.add(tex)
|
|
self.wait()
|
|
|
|
class EoLA3Wrapper(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
rect = ScreenRectangle(height = 5)
|
|
rect.next_to(morty, UP+LEFT)
|
|
rect.to_edge(UP, buff = LARGE_BUFF)
|
|
title = TextMobject("Essence of linear algebra")
|
|
title.next_to(rect, UP)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
FadeIn(title),
|
|
morty.change, "raise_right_hand", rect
|
|
)
|
|
self.wait(4)
|
|
|
|
class FeedForwardCode(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class NeuronIsFunction(MoreHonestMNistNetworkPreview):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"layer_to_layer_buff" : 2
|
|
}
|
|
}
|
|
def construct(self):
|
|
self.setup_network_mob()
|
|
self.activate_network()
|
|
self.write_neuron_holds_a_number()
|
|
self.feed_in_new_image(8, 7)
|
|
self.neuron_is_function()
|
|
self.show_neuron_as_function()
|
|
self.fade_network_back_in()
|
|
self.network_is_a_function()
|
|
self.feed_in_new_image(9, 4)
|
|
self.wait(2)
|
|
|
|
|
|
def setup_network_mob(self):
|
|
self.network_mob.scale(0.7)
|
|
self.network_mob.to_edge(DOWN)
|
|
self.network_mob.shift(LEFT)
|
|
|
|
def activate_network(self):
|
|
network_mob = self.network_mob
|
|
self.image_map = get_organized_images()
|
|
in_vect = self.image_map[3][0]
|
|
mnist_mob = MNistMobject(in_vect)
|
|
mnist_mob.next_to(network_mob, LEFT, MED_LARGE_BUFF, UP)
|
|
activations = self.network.get_activation_of_all_layers(in_vect)
|
|
for i, activation in enumerate(activations):
|
|
layer = self.network_mob.layers[i]
|
|
Transform(
|
|
layer, self.network_mob.get_active_layer(i, activation)
|
|
).update(1)
|
|
self.add(mnist_mob)
|
|
|
|
self.image_rect, self.curr_image = mnist_mob
|
|
|
|
def write_neuron_holds_a_number(self):
|
|
neuron_word = TextMobject("Neuron")
|
|
arrow = Arrow(ORIGIN, DOWN, color = BLUE)
|
|
thing_words = TextMobject("Thing that holds \\\\ a number")
|
|
group = VGroup(neuron_word, arrow, thing_words)
|
|
group.arrange(DOWN)
|
|
group.to_corner(UP+RIGHT, buff = LARGE_BUFF)
|
|
|
|
neuron = self.network_mob.layers[2].neurons[2]
|
|
decimal = DecimalNumber(neuron.get_fill_opacity())
|
|
decimal.set_width(0.7*neuron.get_width())
|
|
decimal.move_to(neuron)
|
|
neuron_group = VGroup(neuron, decimal)
|
|
neuron_group.save_state()
|
|
decimal.set_fill(opacity = 0)
|
|
|
|
self.play(
|
|
neuron_group.restore,
|
|
neuron_group.scale, 3,
|
|
neuron_group.next_to, neuron_word, LEFT,
|
|
FadeIn(neuron_word),
|
|
GrowArrow(arrow),
|
|
FadeIn(
|
|
thing_words, run_time = 2,
|
|
rate_func = squish_rate_func(smooth, 0.3, 1)
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(neuron_group.restore)
|
|
|
|
self.neuron_word = neuron_word
|
|
self.neuron_word_arrow = arrow
|
|
self.thing_words = thing_words
|
|
self.neuron = neuron
|
|
self.decimal = decimal
|
|
|
|
def feed_in_new_image(self, digit, choice):
|
|
in_vect = self.image_map[digit][choice]
|
|
|
|
args = []
|
|
for s in "answer_rect", "curr_image", "image_rect":
|
|
if hasattr(self, s):
|
|
args.append(getattr(self, s))
|
|
else:
|
|
args.append(VectorizedPoint())
|
|
MoreHonestMNistNetworkPreview.reset_display(self, *args)
|
|
self.feed_in_image(in_vect)
|
|
|
|
def neuron_is_function(self):
|
|
thing_words = self.thing_words
|
|
cross = Cross(thing_words)
|
|
function_word = TextMobject("Function")
|
|
function_word.move_to(thing_words, UP)
|
|
|
|
self.play(
|
|
thing_words.fade,
|
|
ShowCreation(cross)
|
|
)
|
|
self.play(
|
|
FadeIn(function_word),
|
|
VGroup(thing_words, cross).to_edge, DOWN,
|
|
)
|
|
self.wait()
|
|
|
|
self.function_word = function_word
|
|
|
|
def show_neuron_as_function(self):
|
|
neuron = self.neuron.copy()
|
|
edges = neuron.edges_in.copy()
|
|
prev_layer = self.network_mob.layers[1].copy()
|
|
|
|
arrow = Arrow(ORIGIN, RIGHT, color = BLUE)
|
|
arrow.next_to(neuron, RIGHT, SMALL_BUFF)
|
|
decimal = DecimalNumber(neuron.get_fill_opacity())
|
|
decimal.next_to(arrow, RIGHT)
|
|
|
|
self.play(
|
|
FadeOut(self.network_mob),
|
|
*list(map(Animation, [neuron, edges, prev_layer]))
|
|
)
|
|
self.play(OldLaggedStart(
|
|
ShowCreationThenDestruction,
|
|
edges.copy().set_stroke(YELLOW, 4),
|
|
))
|
|
self.play(
|
|
GrowArrow(arrow),
|
|
Transform(self.decimal, decimal)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.non_faded_network_parts = VGroup(
|
|
neuron, edges, prev_layer
|
|
)
|
|
self.neuron_arrow = arrow
|
|
|
|
def fade_network_back_in(self):
|
|
anims = [
|
|
FadeIn(
|
|
mob,
|
|
run_time = 2,
|
|
lag_ratio = 0.5
|
|
)
|
|
for mob in (self.network_mob.layers, self.network_mob.edge_groups)
|
|
]
|
|
anims += [
|
|
FadeOut(self.neuron_arrow),
|
|
FadeOut(self.decimal),
|
|
]
|
|
anims.append(Animation(self.non_faded_network_parts))
|
|
|
|
self.play(*anims)
|
|
self.remove(self.non_faded_network_parts)
|
|
|
|
def network_is_a_function(self):
|
|
neuron_word = self.neuron_word
|
|
network_word = TextMobject("Network")
|
|
network_word.set_color(YELLOW)
|
|
network_word.move_to(neuron_word)
|
|
|
|
func_tex = TexMobject(
|
|
"f(a_0, \\dots, a_{783}) = ",
|
|
"""\\left[
|
|
\\begin{array}{c}
|
|
y_0 \\\\ \\vdots \\\\ y_{9}
|
|
\\end{array}
|
|
\\right]"""
|
|
)
|
|
func_tex.to_edge(UP)
|
|
func_tex.shift(MED_SMALL_BUFF*LEFT)
|
|
|
|
self.play(
|
|
ReplacementTransform(neuron_word, network_word),
|
|
FadeIn(func_tex)
|
|
)
|
|
|
|
###
|
|
|
|
def reset_display(self, answer_rect, image, image_rect):
|
|
#Don't do anything, just record these args
|
|
self.answer_rect = answer_rect
|
|
self.curr_image = image
|
|
self.image_rect = image_rect
|
|
return
|
|
|
|
class ComplicationIsReassuring(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"It kind of has to \\\\ be complicated, right?",
|
|
target_mode = "speaking",
|
|
student_index = 0
|
|
)
|
|
self.play(self.teacher.change, "happy")
|
|
self.wait(4)
|
|
|
|
class NextVideo(MoreHonestMNistNetworkPreview, PiCreatureScene):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
"neuron_stroke_color" : WHITE,
|
|
"layer_to_layer_buff" : 2.5,
|
|
"brace_for_large_layers" : False,
|
|
}
|
|
}
|
|
def setup(self):
|
|
MoreHonestMNistNetworkPreview.setup(self)
|
|
PiCreatureScene.setup(self)
|
|
|
|
def construct(self):
|
|
self.network_and_data()
|
|
self.show_next_video()
|
|
self.talk_about_subscription()
|
|
self.show_video_neural_network()
|
|
|
|
def network_and_data(self):
|
|
morty = self.pi_creature
|
|
network_mob = self.network_mob
|
|
network_mob.to_edge(LEFT)
|
|
for obj in network_mob, self:
|
|
obj.remove(network_mob.output_labels)
|
|
network_mob.scale(0.7)
|
|
network_mob.shift(RIGHT)
|
|
edge_update = ContinualEdgeUpdate(network_mob)
|
|
|
|
training_data, validation_data, test_data = load_data_wrapper()
|
|
data_mobs = VGroup()
|
|
for vect, num in test_data[:30]:
|
|
image = MNistMobject(vect)
|
|
image.set_height(0.7)
|
|
arrow = Arrow(ORIGIN, RIGHT, color = BLUE)
|
|
num_mob = TexMobject(str(num))
|
|
group = Group(image, arrow, num_mob)
|
|
group.arrange(RIGHT, buff = SMALL_BUFF)
|
|
group.next_to(ORIGIN, RIGHT)
|
|
data_mobs.add(group)
|
|
|
|
data_mobs.next_to(network_mob, UP)
|
|
|
|
self.add(edge_update)
|
|
self.play(morty.change, "confused", network_mob)
|
|
self.wait(2)
|
|
for data_mob in data_mobs:
|
|
self.add(data_mob)
|
|
self.wait(0.2)
|
|
self.remove(data_mob)
|
|
|
|
self.content = network_mob
|
|
self.edge_update = edge_update
|
|
|
|
def show_next_video(self):
|
|
morty = self.pi_creature
|
|
content = self.content
|
|
|
|
video = VideoIcon()
|
|
video.set_height(3)
|
|
video.set_fill(RED, 0.8)
|
|
video.next_to(morty, UP+LEFT)
|
|
|
|
rect = SurroundingRectangle(video)
|
|
rect.set_stroke(width = 0)
|
|
rect.set_fill(BLACK, 0.5)
|
|
|
|
words = TextMobject("On learning")
|
|
words.next_to(video, UP)
|
|
|
|
if self.edge_update.internal_time < 1:
|
|
self.edge_update.internal_time = 2
|
|
self.play(
|
|
content.set_height, 0.8*video.get_height(),
|
|
content.move_to, video,
|
|
morty.change, "raise_right_hand",
|
|
FadeIn(rect),
|
|
FadeIn(video),
|
|
)
|
|
self.add_foreground_mobjects(rect, video)
|
|
self.wait(2)
|
|
self.play(Write(words))
|
|
self.wait(2)
|
|
|
|
self.video = Group(content, rect, video, words)
|
|
|
|
def talk_about_subscription(self):
|
|
morty = self.pi_creature
|
|
morty.generate_target()
|
|
morty.target.change("hooray")
|
|
morty.target.rotate(
|
|
np.pi, axis = UP, about_point = morty.get_left()
|
|
)
|
|
morty.target.shift(LEFT)
|
|
video = self.video
|
|
|
|
|
|
subscribe_word = TextMobject(
|
|
"Subscribe", "!",
|
|
arg_separator = ""
|
|
)
|
|
bang = subscribe_word[1]
|
|
subscribe_word.to_corner(DOWN+RIGHT)
|
|
subscribe_word.shift(3*UP)
|
|
q_mark = TextMobject("?")
|
|
q_mark.move_to(bang, LEFT)
|
|
arrow = Arrow(ORIGIN, DOWN, color = RED, buff = 0)
|
|
arrow.next_to(subscribe_word, DOWN)
|
|
arrow.shift(MED_LARGE_BUFF * RIGHT)
|
|
|
|
self.play(
|
|
Write(subscribe_word),
|
|
self.video.shift, 3*LEFT,
|
|
MoveToTarget(morty),
|
|
)
|
|
self.play(GrowArrow(arrow))
|
|
self.wait(2)
|
|
self.play(morty.change, "maybe", arrow)
|
|
self.play(Transform(bang, q_mark))
|
|
self.wait(3)
|
|
|
|
def show_video_neural_network(self):
|
|
morty = self.pi_creature
|
|
|
|
network_mob, rect, video, words = self.video
|
|
network_mob.generate_target(use_deepcopy = True)
|
|
network_mob.target.set_height(5)
|
|
network_mob.target.to_corner(UP+LEFT)
|
|
neurons = VGroup(*network_mob.target.layers[-1].neurons[:2])
|
|
neurons.set_stroke(width = 0)
|
|
|
|
video.generate_target()
|
|
video.target.set_fill(opacity = 1)
|
|
video.target.set_height(neurons.get_height())
|
|
video.target.move_to(neurons, LEFT)
|
|
|
|
self.play(
|
|
MoveToTarget(network_mob),
|
|
MoveToTarget(video),
|
|
FadeOut(words),
|
|
FadeOut(rect),
|
|
morty.change, "raise_left_hand"
|
|
)
|
|
|
|
neuron_pairs = VGroup(*[
|
|
VGroup(*network_mob.layers[-1].neurons[2*i:2*i+2])
|
|
for i in range(1, 5)
|
|
])
|
|
for pair in neuron_pairs:
|
|
video = video.copy()
|
|
video.move_to(pair, LEFT)
|
|
pair.target = video
|
|
|
|
self.play(OldLaggedStart(
|
|
MoveToTarget, neuron_pairs,
|
|
run_time = 3
|
|
))
|
|
self.play(morty.change, "shruggie")
|
|
self.wait(10)
|
|
|
|
###
|
|
|
|
class NNPatreonThanks(PatreonThanks):
|
|
CONFIG = {
|
|
"specific_patrons" : [
|
|
"Desmos",
|
|
"Burt Humburg",
|
|
"CrypticSwarm",
|
|
"Juan Benet",
|
|
"Ali Yahya",
|
|
"William",
|
|
"Mayank M. Mehrotra",
|
|
"Lukas Biewald",
|
|
"Samantha D. Suplee",
|
|
"Yana Chernobilsky",
|
|
"Kaustuv DeBiswas",
|
|
"Kathryn Schmiedicke",
|
|
"Yu Jun",
|
|
"Dave Nicponski",
|
|
"Damion Kistler",
|
|
"Markus Persson",
|
|
"Yoni Nazarathy",
|
|
"Ed Kellett",
|
|
"Joseph John Cox",
|
|
"Luc Ritchie",
|
|
"Andy Nichols",
|
|
"Harsev Singh",
|
|
"Mads Elvheim",
|
|
"Erik Sundell",
|
|
"Xueqi Li",
|
|
"David G. Stork",
|
|
"Tianyu Ge",
|
|
"Ted Suzman",
|
|
"Linh Tran",
|
|
"Andrew Busey",
|
|
"Michael McGuffin",
|
|
"John Haley",
|
|
"Ankalagon",
|
|
"Eric Lavault",
|
|
"Boris Veselinovich",
|
|
"Julian Pulgarin",
|
|
"Jeff Linse",
|
|
"Cooper Jones",
|
|
"Ryan Dahl",
|
|
"Mark Govea",
|
|
"Robert Teed",
|
|
"Jason Hise",
|
|
"Meshal Alshammari",
|
|
"Bernd Sing",
|
|
"James Thornton",
|
|
"Mustafa Mahdi",
|
|
"Mathew Bramson",
|
|
"Jerry Ling",
|
|
"Vecht",
|
|
"Shimin Kuang",
|
|
"Rish Kundalia",
|
|
"Achille Brighton",
|
|
"Ripta Pasay",
|
|
]
|
|
}
|
|
|
|
class PiCreatureGesture(PiCreatureScene):
|
|
def construct(self):
|
|
self.play(self.pi_creature.change, "raise_right_hand")
|
|
self.wait(5)
|
|
self.play(self.pi_creature.change, "happy")
|
|
self.wait(4)
|
|
|
|
class IntroduceReLU(IntroduceSigmoid):
|
|
CONFIG = {
|
|
"x_axis_label" : "$a$"
|
|
}
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_title()
|
|
self.add_graph()
|
|
self.old_school()
|
|
self.show_ReLU()
|
|
self.label_input_regions()
|
|
|
|
def old_school(self):
|
|
sigmoid_graph = self.sigmoid_graph
|
|
sigmoid_title = VGroup(
|
|
self.sigmoid_name,
|
|
self.equation
|
|
)
|
|
cross = Cross(sigmoid_title)
|
|
old_school = TextMobject("Old school")
|
|
old_school.to_corner(UP+RIGHT)
|
|
old_school.set_color(RED)
|
|
arrow = Arrow(
|
|
old_school.get_bottom(),
|
|
self.equation.get_right(),
|
|
color = RED
|
|
)
|
|
|
|
self.play(ShowCreation(cross))
|
|
self.play(
|
|
Write(old_school, run_time = 1),
|
|
GrowArrow(arrow)
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
ApplyMethod(
|
|
VGroup(cross, sigmoid_title).shift,
|
|
FRAME_X_RADIUS*RIGHT,
|
|
rate_func = running_start
|
|
),
|
|
FadeOut(old_school),
|
|
FadeOut(arrow),
|
|
)
|
|
self.play(ShowCreation(
|
|
self.sigmoid_graph,
|
|
rate_func = lambda t : smooth(1-t),
|
|
remover = True
|
|
))
|
|
|
|
def show_ReLU(self):
|
|
graph = VGroup(
|
|
Line(
|
|
self.coords_to_point(-7, 0),
|
|
self.coords_to_point(0, 0),
|
|
),
|
|
Line(
|
|
self.coords_to_point(0, 0),
|
|
self.coords_to_point(4, 4),
|
|
),
|
|
)
|
|
graph.set_color(YELLOW)
|
|
char = self.x_axis_label.replace("$", "")
|
|
equation = TextMobject("ReLU($%s$) = max$(0, %s)$"%(char, char))
|
|
equation.shift(FRAME_X_RADIUS*LEFT/2)
|
|
equation.to_edge(UP)
|
|
equation.add_background_rectangle()
|
|
name = TextMobject("Rectified linear unit")
|
|
name.move_to(equation)
|
|
name.add_background_rectangle()
|
|
|
|
self.play(Write(equation))
|
|
self.play(ShowCreation(graph), Animation(equation))
|
|
self.wait(2)
|
|
self.play(
|
|
Write(name),
|
|
equation.shift, DOWN
|
|
)
|
|
self.wait(2)
|
|
|
|
self.ReLU_graph = graph
|
|
|
|
def label_input_regions(self):
|
|
l1, l2 = self.ReLU_graph
|
|
neg_words = TextMobject("Inactive")
|
|
neg_words.set_color(RED)
|
|
neg_words.next_to(self.coords_to_point(-2, 0), UP)
|
|
|
|
pos_words = TextMobject("Same as $f(a) = a$")
|
|
pos_words.set_color(GREEN)
|
|
pos_words.next_to(
|
|
self.coords_to_point(1, 1),
|
|
DOWN+RIGHT
|
|
)
|
|
|
|
self.revert_to_original_skipping_status()
|
|
self.play(ShowCreation(l1.copy().set_color(RED)))
|
|
self.play(Write(neg_words))
|
|
self.wait()
|
|
self.play(ShowCreation(l2.copy().set_color(GREEN)))
|
|
self.play(Write(pos_words))
|
|
self.wait(2)
|
|
|
|
class CompareSigmoidReLUOnDeepNetworks(PiCreatureScene):
|
|
def construct(self):
|
|
morty, lisha = self.morty, self.lisha
|
|
sigmoid_graph = FunctionGraph(
|
|
sigmoid,
|
|
x_min = -5,
|
|
x_max = 5,
|
|
)
|
|
sigmoid_graph.stretch_to_fit_width(3)
|
|
sigmoid_graph.set_color(YELLOW)
|
|
sigmoid_graph.next_to(lisha, UP+LEFT)
|
|
sigmoid_graph.shift_onto_screen()
|
|
sigmoid_name = TextMobject("Sigmoid")
|
|
sigmoid_name.next_to(sigmoid_graph, UP)
|
|
sigmoid_graph.add(sigmoid_name)
|
|
|
|
slow_learner = TextMobject("Slow learner")
|
|
slow_learner.set_color(YELLOW)
|
|
slow_learner.to_corner(UP+LEFT)
|
|
slow_arrow = Arrow(
|
|
slow_learner.get_bottom(),
|
|
sigmoid_graph.get_top(),
|
|
)
|
|
|
|
relu_graph = VGroup(
|
|
Line(2*LEFT, ORIGIN),
|
|
Line(ORIGIN, np.sqrt(2)*(RIGHT+UP)),
|
|
)
|
|
relu_graph.set_color(BLUE)
|
|
relu_graph.next_to(lisha, UP+RIGHT)
|
|
relu_name = TextMobject("ReLU")
|
|
relu_name.move_to(relu_graph, UP)
|
|
relu_graph.add(relu_name)
|
|
|
|
network_mob = NetworkMobject(Network(
|
|
sizes = [6, 4, 5, 4, 3, 5, 2]
|
|
))
|
|
network_mob.scale(0.8)
|
|
network_mob.to_edge(UP, buff = MED_SMALL_BUFF)
|
|
network_mob.shift(RIGHT)
|
|
edge_update = ContinualEdgeUpdate(
|
|
network_mob, stroke_width_exp = 1,
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(sigmoid_name),
|
|
ShowCreation(sigmoid_graph),
|
|
lisha.change, "raise_left_hand",
|
|
morty.change, "pondering"
|
|
)
|
|
self.play(
|
|
Write(slow_learner, run_time = 1),
|
|
GrowArrow(slow_arrow)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeIn(relu_name),
|
|
ShowCreation(relu_graph),
|
|
lisha.change, "raise_right_hand",
|
|
morty.change, "thinking"
|
|
)
|
|
self.play(FadeIn(network_mob))
|
|
self.add(edge_update)
|
|
self.wait(10)
|
|
|
|
|
|
|
|
###
|
|
def create_pi_creatures(self):
|
|
morty = Mortimer()
|
|
morty.shift(FRAME_X_RADIUS*RIGHT/2).to_edge(DOWN)
|
|
lisha = PiCreature(color = BLUE_C)
|
|
lisha.shift(FRAME_X_RADIUS*LEFT/2).to_edge(DOWN)
|
|
self.morty, self.lisha = morty, lisha
|
|
return morty, lisha
|
|
|
|
class ShowAmplify(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
rect = ScreenRectangle(height = 5)
|
|
rect.to_corner(UP+LEFT)
|
|
rect.shift(DOWN)
|
|
email = TextMobject("3blue1brown@amplifypartners.com")
|
|
email.next_to(rect, UP)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
morty.change, "raise_right_hand"
|
|
)
|
|
self.wait(2)
|
|
self.play(Write(email))
|
|
self.play(morty.change, "happy", rect)
|
|
self.wait(10)
|
|
|
|
class Thumbnail(NetworkScene):
|
|
CONFIG = {
|
|
"network_mob_config" : {
|
|
'neuron_stroke_color' : WHITE
|
|
}
|
|
}
|
|
def construct(self):
|
|
network_mob = self.network_mob
|
|
network_mob.set_height(FRAME_HEIGHT - 1)
|
|
for layer in network_mob.layers:
|
|
layer.neurons.set_stroke(width = 5)
|
|
|
|
edge_update = ContinualEdgeUpdate(
|
|
network_mob,
|
|
max_stroke_width = 10,
|
|
stroke_width_exp = 4,
|
|
)
|
|
edge_update.internal_time = 3
|
|
edge_update.update(0)
|
|
|
|
self.add(network_mob)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|