diff --git a/eop/bayes.py b/eop/bayes.py index a756d121..755c6748 100644 --- a/eop/bayes.py +++ b/eop/bayes.py @@ -1091,10 +1091,59 @@ class UpdatePokerPrior(SampleSpaceScene): new_label_kwargs = {"labels" : [label]}, ) +class BayesRuleInMemory(Scene): + def construct(self): + randy = Randolph() + randy.to_corner(DOWN+LEFT) + bubble = ThoughtBubble(height = 4) + bubble.pin_to(randy) + B = "\\text{Belief}" + D = "\\text{Data}" + rule = TexMobject( + "P(", B, "|", D, ")", "=", + "P(", "B", ")", + "{P(", D, "|", B, ")", "\\over", "P(", D, ")}" + ) + rule.highlight_by_tex(B, RED) + rule.highlight_by_tex(D, GREEN) + rule.next_to(randy, RIGHT, LARGE_BUFF, UP) + rule.generate_target() + bubble.add_content(rule.target) + screen_rect = ScreenRectangle() + screen_rect.next_to(randy, UP+RIGHT) + + self.add(randy) + self.play( + LaggedStart(FadeIn, rule), + randy.change, "erm", rule + ) + self.play(Blink(randy)) + self.play( + ShowCreation(bubble), + MoveToTarget(rule), + randy.change, "pondering", + ) + self.dither() + self.play(rule.fade, 0.7, run_time = 2) + self.play(randy.change, "confused", rule) + self.play(Blink(randy)) + self.dither(2) + self.play( + FadeOut(VGroup(bubble, rule)), + randy.change, "pondering", screen_rect, + ) + self.play( + randy.look_at, screen_rect.get_right(), + ShowCreation(screen_rect), + ) + self.dither(4) class NextVideoWrapper(TeacherStudentsScene): + CONFIG = { + "title" : "Next chapter: Bayesian networks" + } def construct(self): - title = TextMobject("Next video: Bayesian networks") + title = TextMobject(self.title) title.scale(0.8) title.to_edge(UP, buff = SMALL_BUFF) screen = ScreenRectangle(height = 4) @@ -1108,7 +1157,10 @@ class NextVideoWrapper(TeacherStudentsScene): self.teacher.change, "raise_right_hand" ) self.play(ShowCreation(screen)) - self.change_student_modes(*["pondering"]*3) + self.change_student_modes( + *["pondering"]*3, + look_at_arg = screen + ) self.play(Animation(screen)) self.dither(5) @@ -1415,21 +1467,24 @@ class MoreExamples(TeacherStudentsScene): class MusicExample(SampleSpaceScene, PiCreatureScene): def construct(self): - self.force_skipping() - self.introduce_musician() self.add_prior() self.record_track() self.add_bottom_conditionl() + self.friend_gives_compliment() + self.friends_dont_like() + self.false_compliment() self.add_top_conditionl() self.get_positive_review() self.restrict_space() self.show_posterior_rectangles() + self.show_prior_rectangle_areas() self.show_posterior_probability() self.intuition_of_positive_feedback() self.make_friends_honest() + self.fade_out_post_rect() self.get_negative_feedback() - self.show_negative_feedback_posterior() + self.compare_prior_to_post_given_negative() self.intuition_of_negative_feedback() def introduce_musician(self): @@ -1438,7 +1493,7 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): randy.arms = randy.get_arm_copies() guitar = randy.guitar = Guitar() guitar.move_to(randy) - guitar.shift(0.35*RIGHT + 0.6*UP) + guitar.shift(0.31*RIGHT + 0.6*UP) randy.change_mode("plain") self.play( @@ -1456,58 +1511,444 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): self.change_pi_creature_with_guitar("concerned_musician") self.dither(2) self.play( - randy.scale, 0.5, + randy.scale, 0.7, randy.to_corner, UP+LEFT, ) self.play_notes(guitar) def add_prior(self): - pass + sample_space = SampleSpace() + sample_space.shift(DOWN) + sample_space.divide_horizontally(0.8, colors = [MAROON_D, BLUE_E]) + labels = VGroup( + TexMobject("P(S) = ", "0.8"), + TexMobject("P(\\text{not } S) = ", "0.2"), + ) + labels.scale(0.7) + braces, labels = sample_space.get_side_braces_and_labels(labels) + VGroup(sample_space, braces, labels).to_edge(LEFT) + words = map(TextMobject, [ + "Blunt honesty", "Some confidence" + ]) + for word, part in zip(words, sample_space.horizontal_parts): + word.scale(0.6) + word.move_to(part) + + self.play(LaggedStart(FadeIn, sample_space, run_time = 1)) + self.play(*map(GrowFromCenter, braces)) + for label in labels: + self.play(Write(label, run_time = 2)) + self.dither() + for word, mode in zip(words, ["maybe", "soulful_musician"]): + self.play(LaggedStart(FadeIn, word, run_time = 1)) + self.change_pi_creature_with_guitar(mode) + self.dither() + self.dither() + self.play(*map(FadeOut, words)) + + self.sample_space = sample_space def record_track(self): - pass + randy = self.pi_creature + friends = VGroup(*[ + PiCreature(mode = "happy", color = color).flip() + for color in BLUE_B, GREY_BROWN, MAROON_E + ]) + friends.scale(0.6) + friends.arrange_submobjects(RIGHT) + friends.next_to(randy, RIGHT, LARGE_BUFF, DOWN) + friends.to_edge(RIGHT) + for friend in friends: + friend.look_at(randy.eyes) + + headphones = VGroup(*map(Headphones, friends)) + + self.play(FadeIn(friends)) + self.pi_creatures.add(*friends) + self.play( + FadeIn(headphones), + Animation(friends) + ) + self.play_notes(randy.guitar) + self.play(LaggedStart( + ApplyMethod, friends, + lambda pi : (pi.change, "hooray"), + run_time = 2, + )) + + self.friends = friends + self.headphones = headphones def add_bottom_conditionl(self): - pass + p = 0.99 + bottom_part = self.sample_space[1] + bottom_part.divide_vertically(p, colors = [GREEN_E, YELLOW]) + label = self.get_conditional_label(p, False) + braces, labels = bottom_part.get_bottom_braces_and_labels([label]) + brace = braces[0] + + self.play(FadeIn(bottom_part.vertical_parts)) + self.play(GrowFromCenter(brace)) + self.play(Write(label)) + self.dither() + + def friend_gives_compliment(self): + friends = self.friends + bubble = SpeechBubble( + height = 1.25, width = 3, direction = RIGHT, + fill_opacity = 0, + ) + content = TextMobject("Phenomenal!") + content.scale(0.75) + bubble.add_content(content) + VGroup(bubble, content).next_to(friends, LEFT, SMALL_BUFF) + VGroup(bubble, content).to_edge(UP, SMALL_BUFF) + + self.play(LaggedStart( + ApplyMethod, friends, + lambda pi : (pi.change_mode, "conniving") + )) + self.dither() + self.play( + ShowCreation(bubble), + Write(bubble.content, run_time = 1), + ApplyMethod(friends[0].change_mode, "hooray"), + LaggedStart( + ApplyMethod, VGroup(*friends[1:]), + lambda pi : (pi.change_mode, "happy") + ), + ) + self.dither(2) + self.play(*map(FadeOut, [bubble, content])) + + def friends_dont_like(self): + friends = self.friends + pi1, pi2, pi3 = friends + for friend in friends: + friend.generate_target() + pi1.target.change("guilty", pi2.eyes) + pi2.target.change("hesitant", pi1.eyes) + pi3.target.change("pondering", pi2.eyes) + + self.play(LaggedStart( + MoveToTarget, friends + )) + self.change_pi_creature_with_guitar("concerned_musician") + self.dither() + + def false_compliment(self): + friend = self.friends[0] + bubble = SpeechBubble( + height = 1.25, width = 4.5, direction = RIGHT, + fill_opacity = 0, + ) + content = TextMobject("The beat was consistent.") + content.scale(0.75) + bubble.add_content(content) + VGroup(bubble, content).next_to(friend, LEFT, SMALL_BUFF) + VGroup(bubble, content).to_edge(UP, SMALL_BUFF) + + self.play( + friend.change_mode, "maybe", + ShowCreation(bubble), + Write(content) + ) + self.change_pi_creature_with_guitar("happy") + self.dither() + self.play(*map(FadeOut, [bubble, content])) + + self.bubble = bubble def add_top_conditionl(self): - pass + p = 0.9 + top_part = self.sample_space[0] + top_part.divide_vertically(p, colors = [TEAL_E, RED_E]) + label = self.get_conditional_label(p, True) + braces, labels = top_part.get_top_braces_and_labels([label]) + brace = braces[0] + + self.play(FadeIn(top_part.vertical_parts)) + self.play(GrowFromCenter(brace)) + self.play(Write(label, run_time = 2)) + self.dither() def get_positive_review(self): - pass + friends = self.friends + + self.change_pi_creature_with_guitar( + "soulful_musician", + LaggedStart( + ApplyMethod, friends, + lambda pi : (pi.change, "happy"), + run_time = 1, + ) + ) + self.play_notes(self.pi_creature.guitar) def restrict_space(self): - pass + positive_space, negative_space = [ + VGroup(*[ + self.sample_space[i][j] + for i in range(2) + ]) + for j in range(2) + ] + negative_space.save_state() + + self.play(negative_space.fade, 0.8) + self.play(LaggedStart( + ApplyMethod, positive_space, + lambda m : (m.highlight, YELLOW), + rate_func = there_and_back, + run_time = 2, + lag_ratio = 0.7, + )) + self.dither() + + self.negative_space = negative_space def show_posterior_rectangles(self): - pass + prior_rects = self.get_prior_rectangles() + post_rects = self.get_posterior_rectangles() + label = TexMobject("P(S | ", "\\checkmark", ")") + label.scale(0.7) + label.highlight_by_tex("\\checkmark", GREEN) + braces, labels = self.get_posterior_rectangle_braces_and_labels( + post_rects, [label] + ) + brace = braces[0] + + self.play(ReplacementTransform( + prior_rects.copy(), post_rects, + run_time = 2 + )) + self.play(GrowFromCenter(brace)) + self.play(Write(label)) + self.dither() + + self.post_rects = post_rects + self.post_tex = label + + def show_prior_rectangle_areas(self): + prior_rects = self.get_prior_rectangles() + products = VGroup( + TexMobject("(", "0.8", ")(", "0.9", ")"), + TexMobject("(", "0.2", ")(", "0.99", ")"), + ) + top_product, bottom_product = products + for product, rect in zip(products, prior_rects): + product.scale(0.7) + product.move_to(rect) + side_labels = self.sample_space.horizontal_parts.labels + top_labels = self.sample_space[0].vertical_parts.labels + bottom_labels = self.sample_space[1].vertical_parts.labels + + self.play( + ReplacementTransform( + side_labels[0][-1].copy(), + top_product[1], + ), + ReplacementTransform( + top_labels[0][-1].copy(), + top_product[3], + ), + Write(VGroup(*top_product[::2])) + ) + self.dither(2) + self.play( + ReplacementTransform( + side_labels[1][-1].copy(), + bottom_product[1], + ), + ReplacementTransform( + bottom_labels[0][-1].copy(), + bottom_product[3], + ), + Write(VGroup(*bottom_product[::2])) + ) + self.dither(2) + + self.products = products def show_posterior_probability(self): - pass + post_tex = self.post_tex + rhs = TexMobject("\\approx", "0.78") + rhs.scale(0.7) + rhs.next_to(post_tex, RIGHT) + ratio = TexMobject( + "{(0.8)(0.9)", "\\over", + "(0.8)(0.9)", "+", "(0.2)(0.99)}" + ) + ratio.scale(0.6) + ratio.next_to(VGroup(post_tex, rhs), DOWN, LARGE_BUFF) + ratio.to_edge(RIGHT) + arrow_kwargs = { + "tip_length" : 0.15, + "color" : WHITE, + "buff" : 2*SMALL_BUFF, + } + to_ratio_arrow = Arrow( + post_tex.get_bottom(), ratio.get_top(), **arrow_kwargs + ) + to_rhs_arrow = Arrow( + ratio.get_top(), rhs[1].get_bottom(), **arrow_kwargs + ) + + self.play( + ShowCreation(to_ratio_arrow), + FadeIn(ratio) + ) + self.dither() + self.play(ShowCreation(to_rhs_arrow)) + self.play(Write(rhs, run_time = 1)) + self.dither(2) + + self.post_rhs = rhs + self.ratio_group = VGroup(ratio, to_ratio_arrow, to_rhs_arrow) def intuition_of_positive_feedback(self): - pass + friends = self.friends + prior_num = self.sample_space.horizontal_parts.labels[0][-1] + post_num = self.post_rhs[-1] + prior_rect = SurroundingRectangle(prior_num) + post_rect = SurroundingRectangle(post_num) + dot = Dot(prior_rect.get_center()) + dot.set_fill(WHITE, 0.5) + + self.play(ShowCreation(prior_rect)) + self.play( + dot.move_to, post_rect, + dot.set_fill, None, 0, + path_arc = -np.pi/6, + run_time = 2, + ) + self.play(ShowCreation(post_rect)) + self.dither(2) + for mode, time in ("shruggie", 2), ("hesitant", 0): + self.play(LaggedStart( + ApplyMethod, friends, + lambda pi : (pi.change, mode), + run_time = 2, + )) + self.dither(time) + self.play(*map(FadeOut, [ + prior_rect, post_rect, + self.ratio_group, self.post_rhs + ])) + + self.prior_num_rect = prior_rect def make_friends_honest(self): - pass + post_rects = self.post_rects + + self.play(FadeOut(self.products)) + for value in 0.5, 0.1, 0.9: + label = self.get_conditional_label(value) + self.play(*self.get_top_conditional_change_anims( + value, post_rects, + new_label_kwargs = {"labels" : [label]}, + ), run_time = 2) + self.dither(2) + + def fade_out_post_rect(self): + self.play(*map(FadeOut, [ + self.post_rects, + self.post_rects.braces, + self.post_rects.labels, + ])) + self.play(self.negative_space.restore) def get_negative_feedback(self): - pass + friends = self.friends + old_prior_rects = self.get_prior_rectangles() + for part in self.sample_space.horizontal_parts: + part.vertical_parts.submobjects.reverse() + new_prior_rects = self.get_prior_rectangles() + post_rects = self.get_posterior_rectangles() + label = TexMobject( + "P(S | \\text{not } ", "\\checkmark", ")", + "\\approx", "0.98" + ) + label.scale(0.7) + label.highlight_by_tex("\\checkmark", GREEN) + braces, labels = self.get_posterior_rectangle_braces_and_labels( + post_rects, [label] + ) + brace = braces[0] - def show_negative_feedback_posterior(self): - pass + self.play(old_prior_rects.fade, 0.8) + self.play(LaggedStart( + ApplyMethod, friends, + lambda pi : (pi.change, "pondering", post_rects), + run_time = 1 + )) + self.dither() + self.play(ReplacementTransform( + new_prior_rects.copy(), post_rects, + run_time = 2 + )) + self.play(GrowFromCenter(brace)) + self.dither(2) + self.play(Write(label)) + self.dither(3) + + self.post_rects = post_rects + + def compare_prior_to_post_given_negative(self): + post_num = self.post_rects.labels[0][-1] + post_num_rect = SurroundingRectangle(post_num) + + self.play(ShowCreation(self.prior_num_rect)) + self.dither() + self.play(ShowCreation(post_num_rect)) + self.dither() + + self.post_num_rect = post_num_rect def intuition_of_negative_feedback(self): - pass + friends = self.friends + randy = self.pi_creature + bubble = self.bubble + + modes = ["sassy", "pleading", "horrified"] + for friend, mode in zip(friends, modes): + friend.generate_target() + friend.target.change(mode, randy.eyes) + content = TextMobject("Horrible. Just horrible.") + content.scale(0.6) + bubble.add_content(content) + + self.play(*map(MoveToTarget, friends)) + self.play( + ShowCreation(bubble), + Write(bubble.content) + ) + self.change_pi_creature_with_guitar("sad") + self.dither() + self.change_pi_creature_with_guitar("concerned_musician") + self.dither(3) ###### def create_pi_creature(self): randy = Randolph() randy.left_arm_range = [.36, .45] + self.randy = randy return randy - def change_pi_creature_with_guitar(self, target_mode): + def get_conditional_label(self, value, given_suck = True): + positive_str = "\\checkmark" + label = TexMobject( + "P(", positive_str, "|", + "" if given_suck else "\\text{not }", + "S", ")", + "=", str(value) + ) + label.highlight_by_tex(positive_str, GREEN) + label.scale(0.7) + return label + + def change_pi_creature_with_guitar(self, target_mode, *added_anims): randy = self.pi_creature randy.remove(randy.arms, randy.guitar) target = randy.copy() @@ -1516,7 +1957,7 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): target.guitar = randy.guitar.copy() for pi in randy, target: pi.add(pi.guitar, pi.arms) - self.play(Transform(randy, target)) + self.play(Transform(randy, target), *added_anims) def play_notes(self, guitar): note = SVGMobject(file_name = "8th_note") @@ -1524,6 +1965,7 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): note.set_stroke(width = 0) note.set_fill(BLUE, 1) note.move_to(guitar) + note.shift(MED_SMALL_BUFF*(DOWN+2*LEFT)) notes = VGroup(*[note.copy() for x in range(10)]) sine_wave = FunctionGraph(np.sin, x_min = -5, x_max = 5) sine_wave.scale(0.75) @@ -1541,9 +1983,95 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): rate_func = lambda t : t, )) - - - +class FinalWordsOnRule(SampleSpaceScene): + def construct(self): + self.add_sample_space() + self.add_uses() + self.tweak_values() + + def add_sample_space(self): + sample_space = self.sample_space = SampleSpace() + prior = 0.2 + top_conditional = 0.8 + bottom_condional = 0.3 + sample_space.divide_horizontally(prior) + sample_space[0].divide_vertically( + top_conditional, colors = [GREEN, RED] + ) + sample_space[1].divide_vertically( + bottom_condional, colors = [GREEN_E, RED_E] + ) + B = "\\text{Belief}" + D = "\\text{Data}" + P_B = TexMobject("P(", B, ")") + P_D_given_B = TexMobject("P(", D, "|", B, ")") + P_D_given_not_B = TexMobject( + "P(", D, "|", "\\text{not }", B, ")" + ) + P_B_given_D = TexMobject("P(", B, "|", D, ")") + labels = VGroup(P_B, P_D_given_B, P_D_given_not_B, P_B_given_D) + for label in labels: + label.scale(0.7) + label.highlight_by_tex(B, BLUE) + label.highlight_by_tex(D, GREEN) + + prior_rects = self.get_prior_rectangles() + post_rects = self.get_posterior_rectangles() + for i in range(2): + sample_space[i][1].fade(0.7) + + braces = VGroup() + bs, ls = sample_space.get_side_braces_and_labels([P_B]) + braces.add(*bs) + bs, ls = sample_space[0].get_top_braces_and_labels([P_D_given_B]) + braces.add(*bs) + bs, ls = sample_space[1].get_bottom_braces_and_labels([P_D_given_not_B]) + braces.add(*bs) + bs, ls = self.get_posterior_rectangle_braces_and_labels( + post_rects, [P_B_given_D] + ) + braces.add(*bs) + + group = VGroup(sample_space, braces, labels, post_rects) + group.to_corner(DOWN + LEFT) + self.add(group) + + self.post_rects = post_rects + + def add_uses(self): + uses = TextMobject( + "Machine learning, ", + "scientific inference, $\\dots$", + ) + uses.to_edge(UP) + for use in uses: + self.play(Write(use, run_time = 2)) + self.dither() + + def tweak_values(self): + post_rects = self.post_rects + new_value_lists = [ + (0.85, 0.1, 0.11), + (0.3, 0.9, 0.4), + (0.97, 0.3, 1./22), + ] + for new_values in new_value_lists: + for i, value in zip(range(2), new_values): + self.play(*self.get_conditional_change_anims( + i, value, post_rects + )) + self.dither() + self.play(*it.chain( + self.get_horizontal_division_change_animations(new_values[-1]), + self.get_posterior_rectangle_change_anims(post_rects) + )) + self.dither() + self.dither(2) + +class FootnoteWrapper(NextVideoWrapper): + CONFIG = { + "title" : "Thoughts on the classic Bayes example" + } diff --git a/topics/objects.py b/topics/objects.py index 7226ce75..ef1bf7ae 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -38,7 +38,6 @@ class SunGlasses(SVGMobject): ) self.move_to(pi_creature.eyes, UP) - class Speedometer(VMobject): CONFIG = { "arc_angle" : 4*np.pi/3, @@ -280,13 +279,18 @@ class Headphones(SVGMobject): "y_stretch_factor" : 0.5, "color" : GREY, } - def __init__(self, **kwargs): + def __init__(self, pi_creature = None, **kwargs): digest_config(self, kwargs) SVGMobject.__init__(self, file_name = self.file_name, **kwargs) self.stretch(self.y_stretch_factor, 1) self.scale_to_fit_height(self.height) self.set_stroke(width = 0) self.set_fill(color = self.color) + if pi_creature is not None: + eyes = pi_creature.eyes + self.scale_to_fit_height(3*eyes.get_height()) + self.move_to(eyes, DOWN) + self.shift(DOWN*eyes.get_height()/4) class Clock(VGroup): CONFIG = { diff --git a/topics/probability.py b/topics/probability.py index 590d61d6..d162d5be 100644 --- a/topics/probability.py +++ b/topics/probability.py @@ -156,7 +156,7 @@ class SampleSpace(VGroup): "height" : 3, "width" : 3, "fill_color" : DARK_GREY, - "fill_opacity" : 0.8, + "fill_opacity" : 1, "stroke_width" : 0.5, "stroke_color" : LIGHT_GREY, }, @@ -204,7 +204,7 @@ class SampleSpace(VGroup): def get_horizontal_division( self, p_list, - colors = [GREEN_E, BLUE], + colors = [GREEN_E, BLUE_E], vect = DOWN ): return self.get_division_along_dimension(p_list, 1, colors, vect)