1. Model for Neural Collaborative Filtering (NCF) This model is a specialized Multi-Layer Perceptron (MLP). It's not a generic MLP; its input structure is specifically designed to handle users and items. It's definitely not a CNN, as there is no spatial grid-like data (like in an image). The Core Idea: Instead of a simple dot product (like in Matrix Factorization), we use a neural network to learn the complex, non-linear function that describes how user tastes and item properties interact. Pseudo-code for the NCF Model and Training codePython # --- 1. Define Model Parameters --- num_users = 10000 num_items = 5000 embedding_size = 32 # This is the size of the "DNA" vector, a hyperparameter # --- 2. Define the NCF Model Architecture --- class NCF_Model: def __init__(self): # Create embedding layers. These are just lookup tables. # When given a user_id (e.g., 5), it returns the 32-dim vector for that user. self.user_embedding_layer = EmbeddingLayer(input_dim=num_users, output_dim=embedding_size) self.item_embedding_layer = EmbeddingLayer(input_dim=num_items, output_dim=embedding_size) # Define the MLP tower that processes the combined embeddings self.mlp_tower = Sequential([ DenseLayer(input_dim=embedding_size * 2, output_dim=64, activation='relu'), DenseLayer(input_dim=64, output_dim=32, activation='relu'), DenseLayer(input_dim=32, output_dim=16, activation='relu'), DenseLayer(input_dim=16, output_dim=1) # Final output is a single rating value ]) def forward_pass(self, user_id, item_id): # 1. Look up the "DNA" vectors for the current user and item user_vector = self.user_embedding_layer.lookup(user_id) # Shape: (1, 32) item_vector = self.item_embedding_layer.lookup(item_id) # Shape: (1, 32) # 2. Concatenate the two vectors to form the input for the MLP concatenated_vector = concatenate(user_vector, item_vector) # Shape: (1, 64) # 3. Feed the combined vector through the MLP to get the prediction predicted_rating = self.mlp_tower.predict(concatenated_vector) # Shape: (1, 1) return predicted_rating # --- 3. Training Loop --- model = NCF_Model() loss_function = MeanSquaredError() optimizer = AdamOptimizer(learning_rate=0.001) for epoch in range(num_epochs): # 'data' is a list of (user_id, item_id, true_rating) triplets for user_id, item_id, true_rating in training_data: # Get the model's prediction predicted_rating = model.forward_pass(user_id, item_id) # Calculate the error (loss) loss = loss_function.calculate(predicted_rating, true_rating) # Calculate gradients and update the weights of the embedding layers and the MLP gradients = loss.calculate_gradients() optimizer.update_weights(model.parameters, gradients) Creating a story and visualizing it makes the abstract concepts concrete. Here is a comprehensive package for your Neural Collaborative Filtering video. It includes: The Context & Story: A narrative that walks through the process step-by-step, using our familiar characters (Alice, Bob, etc.). This can serve as your voiceover script. The Manim Code: A complete, well-commented Manim script that animates the story, showing the data flow, the model architecture, and the final prediction. 1. The Context and Story (Video Narrative) (Scene 1: The Problem) "Hi everyone. Today, we're going to dive into one of the most powerful techniques in modern recommender systems: Neural Collaborative Filtering, or NCF." "Let's start with a familiar problem. We have a list of users and their movie ratings. Our goal is to predict the missing ratings, like what our user, Alice, would think of the movie Shrek." (Scene 2: The Limitation of Simple Methods) "In the past, we might have tried to solve this by finding users with similar tastes to Alice—her 'taste twins'. This works, but it's a bit shallow. It doesn't understand the deep reasons why Alice likes what she likes." "That's where machine learning comes in. Instead of just comparing raw ratings, what if we could learn the fundamental 'DNA' of every user and every movie?" (Scene 3: The Core Idea - Learning "DNA") "We can imagine every user has a 'Taste DNA'—a short list of numbers that describes their preferences. For Alice, who loves Sci-Fi, her DNA might have a high score for 'Sci-Fi/Action' and a low score for 'Family/Animation'." "Likewise, every movie has a 'Property DNA'. The Matrix would have a high Sci-Fi score, while Shrek would have a high Family film score." "The goal of Neural Collaborative Filtering is to learn these hidden DNA vectors for every user and item simultaneously. The model that does this is a special kind of neural network." (Scene 4: The NCF Architecture - The Animation) "So how does it work? Let's trace the journey of a single prediction." "First, we take the simple IDs for our user and item: User 'Alice' and Item 'Shrek'." "These IDs are fed into two special layers called Embedding Layers. You can think of these as massive lookup tables. The User Embedding layer contains the learned 'Taste DNA' for every single user. When we input Alice's ID, it looks up and pulls out her unique vector." "Simultaneously, the Item Embedding layer looks up Shrek's ID and pulls out its unique 'Property DNA' vector." "Now we have two dense vectors. To combine them, we simply concatenate them—stacking them together to form a single, longer input vector. This vector now contains all the learned information about both Alice's tastes and Shrek's properties." "This combined vector is then fed into the 'brain' of our model: a standard Multi-Layer Perceptron (MLP). This network is designed to learn the incredibly complex, non-linear relationships between a user's taste and a movie's properties. It's here that the model learns sophisticated rules like 'this user likes action, but only when it's not a comedy'." "As the data flows through the MLP, it's transformed at each layer, until finally, it emerges as a single output number." "And that number is our prediction: the model predicts Alice would rate Shrek a 1.2 out of 5!" (Scene 5: The Magic of Training) "You might be wondering, 'how did the model learn the right DNA values in the first place?' It does this through training. The model makes a prediction, compares it to the true rating from our data, measures the error, and slightly adjusts all the DNA vectors and the MLP weights to make the error smaller. It repeats this millions of times until the DNA vectors become incredibly accurate representations of taste." (Scene 6: Conclusion) "So, by transforming users and items into rich, dense 'DNA' vectors and learning the complex interactions between them, Neural Collaborative Filtering can make remarkably accurate predictions, powering the recommendations we see every day." 2. The Manim Code for the Animation This script animates the story above. It uses a Table to represent our sample data and follows the narrative arc step-by-step. code Python # Save this as ncf_story_animation.py # Run with: manim -pql ncf_story_animation.py NCFExplained from manim import * class NCFExplained(Scene): def construct(self): # --- SCENE 1: THE PROBLEM --- title = Text("Neural Collaborative Filtering").scale(1.2) self.play(Write(title)) self.wait(2) self.play(FadeOut(title)) # Create the familiar User-Item Matrix from our data table_data = [ ["User", "Star Wars", "The Matrix", "Titanic", "Shrek", "Frozen"], ["Alice", "5", "5", "1", "?", "1"], ["Bob", "5", "4", "", "2", "2"], ["Charlie", "", "2", "5", "5", ""], ["David", "2", "", "5", "4", "5"], ] table = Table(table_data, include_outer_lines=True).scale(0.5) problem_text = Text("How to predict Alice's rating for Shrek?").next_to(table, UP) self.play(FadeIn(table), Write(problem_text)) # Highlight the question mark question_mark_cell = table.get_entries((2, 5)) highlight_box = SurroundingRectangle(question_mark_cell, color=YELLOW, buff=0.1) self.play(Create(highlight_box)) self.wait(3) self.play(FadeOut(table, problem_text, highlight_box)) # --- SCENE 3: THE CORE IDEA - "DNA" VECTORS --- idea_text = Text("The Core Idea: Learn hidden 'DNA' vectors").to_edge(UP) self.play(Write(idea_text)) # Alice's DNA alice_label = Text("Alice's 'Taste DNA'", color=BLUE).shift(LEFT*3.5 + UP*1) alice_dna = Matrix([[0.9], [-0.8]], h_buff=1).next_to(alice_label, DOWN) alice_dna_labels = VGroup( MathTex(r"\text{Sci-Fi}", font_size=24).next_to(alice_dna.get_rows()[0], LEFT), MathTex(r"\text{Family}", font_size=24).next_to(alice_dna.get_rows()[1], LEFT) ) # Shrek's DNA shrek_label = Text("Shrek's 'Property DNA'", color=GREEN).shift(RIGHT*3.5 + UP*1) shrek_dna = Matrix([[-0.7], [0.9]], h_buff=1).next_to(shrek_label, DOWN) shrek_dna_labels = VGroup( MathTex(r"\text{Sci-Fi}", font_size=24).next_to(shrek_dna.get_rows()[0], LEFT), MathTex(r"\text{Family}", font_size=24).next_to(shrek_dna.get_rows()[1], LEFT) ) self.play(FadeIn(alice_label, alice_dna, alice_dna_labels, scale=0.5)) self.play(FadeIn(shrek_label, shrek_dna, shrek_dna_labels, scale=0.5)) self.wait(4) self.play(FadeOut(VGroup(idea_text, alice_label, alice_dna, alice_dna_labels, shrek_label, shrek_dna, shrek_dna_labels))) # --- SCENE 4: THE NCF ARCHITECTURE --- # Inputs user_input = Text("User: Alice", color=BLUE).scale(0.8).to_edge(UP, buff=1).shift(LEFT*4) item_input = Text("Item: Shrek", color=GREEN).scale(0.8).to_edge(UP, buff=1).shift(RIGHT*4) self.play(Write(user_input), Write(item_input)) # Embedding Layers user_embedding_layer = self.create_embedding_table("User Embeddings", color=BLUE).scale(0.7).shift(LEFT*4) item_embedding_layer = self.create_embedding_table("Item Embeddings", color=GREEN).scale(0.7).shift(RIGHT*4) self.play(FadeIn(user_embedding_layer), FadeIn(item_embedding_layer)) # Lookup user_lookup_arrow = Arrow(user_input.get_bottom(), user_embedding_layer.get_top(), buff=0.2) item_lookup_arrow = Arrow(item_input.get_bottom(), item_embedding_layer.get_top(), buff=0.2) self.play(GrowArrow(user_lookup_arrow), GrowArrow(item_lookup_arrow)) user_vector = Matrix([[0.91], [-0.82], [0.11]], element_alignment_corner=LEFT).scale(0.6).shift(LEFT*2.5 + DOWN*1.5) item_vector = Matrix([[-0.75], [0.89], [0.23]], element_alignment_corner=LEFT).scale(0.6).shift(RIGHT*2.5 + DOWN*1.5) self.play( ReplacementTransform(user_embedding_layer.copy(), user_vector), ReplacementTransform(item_embedding_layer.copy(), item_vector) ) self.play(FadeOut(user_embedding_layer, item_embedding_layer, user_lookup_arrow, item_lookup_arrow, user_input, item_input)) self.wait(1) # Concatenation concat_group = VGroup(user_vector, item_vector).move_to(ORIGIN) self.play( user_vector.animate.next_to(item_vector, LEFT, buff=-0.7), item_vector.animate.next_to(user_vector, RIGHT, buff=-1.7) # Hacky alignment for visual effect ) combined_vector = VGroup(user_vector, item_vector).move_to(ORIGIN).shift(DOWN*1.5) self.play(combined_vector.animate.arrange(DOWN, buff=0.1)) brace = Brace(combined_vector, direction=LEFT) concat_label = brace.get_text("Concatenated Vector") self.play(GrowFromCenter(brace), Write(concat_label)) self.wait(2) # MLP "Brain" mlp = self.create_mlp([6, 8, 4, 1]) mlp_label = Text("MLP 'Brain'").next_to(mlp, UP) self.play(FadeOut(brace, concat_label)) self.play( combined_vector.animate.scale(0.5).next_to(mlp, LEFT, buff=0.5), Create(mlp), Write(mlp_label) ) self.wait(1) # Forward Pass self.play_network_flow(combined_vector, mlp[0]) for i in range(len(mlp) - 1): self.play_network_flow(mlp[i], mlp[i+1]) # Final prediction prediction_val = DecimalNumber(1.23, num_decimal_places=2) prediction_label = Text("Predicted Rating: ").next_to(prediction_val, LEFT) prediction_group = VGroup(prediction_label, prediction_val).scale(0.8).next_to(mlp, RIGHT, buff=1) arrow_to_pred = Arrow(mlp[-1].get_center(), prediction_group.get_left(), buff=0.2) self.play(GrowArrow(arrow_to_pred), FadeIn(prediction_group, scale=1.5)) self.wait(3) # --- SCENE 5: BACK TO THE MATRIX --- self.play(FadeOut(VGroup(combined_vector, mlp, mlp_label, arrow_to_pred))) self.play( prediction_group.animate.scale(0.5 / 0.8).move_to(question_mark_cell.get_center()) ) table_final = Table(table_data).scale(0.5) # Recreate for clean look self.play(FadeIn(table_final)) # Remove the Text part of the prediction group before transforming self.play(ReplacementTransform(prediction_group[1], table_final.get_entries((2,5)))) self.wait(3) def create_embedding_table(self, title, color): return VGroup( Text(title, font_size=24), Rectangle(height=2, width=1.5, color=color, stroke_width=3), Text("...", font_size=48).rotate(PI/2) ).arrange(DOWN, buff=0.2) def create_mlp(self, layer_sizes): layers = VGroup() for size in layer_sizes: layer = VGroup(*[Dot(radius=0.1) for _ in range(size)]) layer.arrange(DOWN, buff=0.25) layers.add(layer) layers.arrange(RIGHT, buff=1) return layers def play_network_flow(self, layer1, layer2): lines = VGroup(*[Line(n1, n2, stroke_width=1.5) for n1 in layer1 for n2 in layer2]) self.play(ShowPassingFlash(lines.set_color(YELLOW), time_width=0.3), run_time=0.5)

视频信息