In the print dialog, choose "Save as PDF" as the destination.
Machine Learning, Week 9
Content-Based Recommendations
Describe every movie by its words, measure how alike two movies are with cosine similarity, and recommend the closest ones.
Objectives
- ▸Explain how a content-based recommender picks movies: similar content, recommended first
- ▸Turn text into count vectors with
CountVectorizer, and say whatstop_wordsandmin_dfdo - ▸Compute cosine similarity by hand: dot product, lengths, division
- ▸Build the similarity matrix with
cosine_similarityand read one row of it - ▸Trace
get_recommendationsline by line and explain where it goes wrong - ▸Prepare your team's project presentation for this week's discussion
Week 9 of the plan
Where This Sits in the Course
- ▸Notebook: Movie Recommendations, a database of 4,803 movies with genres, keywords, cast and director
- ▸Real-world scenario: movie recommendations, a model that takes a movie title and returns similar movies
- ▸Project milestone: Project Teams Presentations and Discussions
Plan for the Two Hours
| Part | What we do | Time |
|---|---|---|
| 1 | The idea, and a worked example by hand | 35 min |
| 2 | The notebook: vectors and the similarity matrix | 25 min |
| 3 | get_recommendations and where it goes wrong | 20 min |
| 4 | Project presentations and discussion | 10 min |
| 5 | Practice with answers, then takeaways | 30 min |
Part 1
The Idea
Similar content, similar taste
What Is a Content-Based Recommender?
- Content-based recommendation
- Recommend products, such as books and movies, based on other products that you like, by measuring how similar the products themselves are.
The Recipe in Four Steps
- 1Describe each movie as one text: its title, genres, keywords, cast and director
- 2Vectorize: count how many times each vocabulary word appears in that text
- 3Compare: compute the cosine similarity of every pair of movies
- 4Recommend: for a liked movie, return the movies with the highest similarity
From Text to Counts: CountVectorizer
texts = ['Action Adventure Fantasy Science Fiction',
'Adventure Fantasy Action']
cv = CountVectorizer(stop_words='english')
m = cv.fit_transform(texts)
print(cv.get_feature_names_out())
print(m.toarray())['action' 'adventure' 'fantasy' 'fiction' 'science']
[[1 1 1 1 1]
[1 1 1 0 0]]- ▸
fitbuilds the vocabulary: every distinct word, lowercased, sorted - ▸
transformgives each text one row of counts, one column per word - ▸
stop_words='english'drops very common words such astheandof - ▸Word order is lost: only the counts remain
Cosine Similarity
- ▸
AandBare the count vectors of two movies,nis the number of vocabulary words - ▸
a_iandb_iare how many times wordiappears in each movie - ▸
A · Bis the dot product,‖A‖is the length ofA
Reading a Cosine
- ▸1: the two vectors point the same way, the same mix of words
- ▸0: the two movies share no word at all
- ▸Counts are never negative, so for us the cosine is always between 0 and 1
- ▸Only the direction matters: doubling every count of a movie leaves its cosine unchanged
By hand
Worked Example: Five Movies
The first five rows of movies.csv, using only the genres column. A viewer liked Avatar: which movie do we recommend?
| Movie | genres |
|---|---|
| Avatar | Action Adventure Fantasy Science Fiction |
| Pirates of the Caribbean: At World's End | Adventure Fantasy Action |
| Spectre | Action Adventure Crime |
| The Dark Knight Rises | Action Crime Drama Thriller |
| John Carter | Action Adventure Science Fiction |
Step 1: The Vocabulary
['action' 'adventure' 'crime' 'drama' 'fantasy' 'fiction' 'science' 'thriller']Columns in vocabulary order
Step 1: The Count Vectors
action, adventure, crime, drama, fantasy, fiction, science, thriller
| Movie | Count vector | Words |
|---|---|---|
| Avatar | [1 1 0 0 1 1 1 0] | 5 |
| Pirates of the Caribbean | [1 1 0 0 1 0 0 0] | 3 |
| Spectre | [1 1 1 0 0 0 0 0] | 3 |
| The Dark Knight Rises | [1 0 1 1 0 0 0 1] | 4 |
| John Carter | [1 1 0 0 0 1 1 0] | 4 |
Step 2: The Lengths
| Movie | Sum of squares | Length ‖v‖ |
|---|---|---|
| Avatar | 5 | √5 = 2.2361 |
| Pirates of the Caribbean | 3 | √3 = 1.7321 |
| Spectre | 3 | √3 = 1.7321 |
| The Dark Knight Rises | 4 | √4 = 2.0000 |
| John Carter | 4 | √4 = 2.0000 |
Step 3: Dot Products with Avatar
| Movie | Shared words | A · B |
|---|---|---|
| Pirates of the Caribbean | action, adventure, fantasy | 3 |
| Spectre | action, adventure | 2 |
| The Dark Knight Rises | action | 1 |
| John Carter | action, adventure, fiction, science | 4 |
A = Avatar, B = Pirates of the Caribbean
Step 4: One Cosine
A = Avatar in every row
Step 4: The Other Three
| Movie B | Computation | cos(A, B) |
|---|---|---|
| Spectre | 2 / (2.2361 × 1.7321) = 2 / 3.8730 | 0.5164 |
| The Dark Knight Rises | 1 / (2.2361 × 2) = 1 / 4.4721 | 0.2236 |
| John Carter | 4 / (2.2361 × 2) = 4 / 4.4721 | 0.8944 |
Step 5: Rank and Recommend
| Rank | Movie | cos(A, B) |
|---|---|---|
| 1 | John Carter | 0.8944 |
| 2 | Pirates of the Caribbean | 0.7746 |
| 3 | Spectre | 0.5164 |
| 4 | The Dark Knight Rises | 0.2236 |
Check with scikit-learn
from sklearn.metrics.pairwise import cosine_similarity
cv = CountVectorizer(stop_words='english')
m = cv.fit_transform(df['genres'].head(5))
sim = cosine_similarity(m)
sim[0]array([1. , 0.77459667, 0.51639778,
0.2236068 , 0.89442719])
Try It: Cosine Step by Step
Part 2
The Notebook on 4,803 Movies
Vectors and the similarity matrix
Open the Notebook in Colab
https://colab.research.google.com/github/
jeffprosise/Machine-Learning/blob/master/
Movie%20Recommendations.ipynbLoad the Data
import pandas as pd
url = ('https://raw.githubusercontent.com/'
'jeffprosise/Machine-Learning/'
'master/Data/movies.csv')
df = pd.read_csv(url)
df.shape- ▸
(4803, 24): 4,803 movies, 24 columns - ▸Among them:
title,genres,keywords,cast,director - ▸Also budget, revenue, runtime, votes: not used by this model
- ▸The file is about 23 MB, so the cell takes a few seconds
Keep Five Columns, Fill the Gaps
df = df[['title', 'genres', 'keywords', 'cast', 'director']]
df = df.fillna('') # Fill missing values with empty stringsOne Text per Movie: the features Column
df['features'] = (df['title'] + ' ' + df['genres'] + ' ' +
df['keywords'] + ' ' + df['cast'] + ' ' +
df['director'])- ▸Five columns glued into one string, with a space between them
- ▸Names become ordinary words: a director's first and last name are two words
- ▸Example: the title
The Dark Knight Risesgivesdark,knight,rises(theis a stop word)
Vectorize All 4,803 Movies
vectorizer = CountVectorizer(stop_words='english', min_df=20)
word_matrix = vectorizer.fit_transform(df['features'])
word_matrix.shapeSame features column, stop_words='english'
How min_df Shapes the Vocabulary
| min_df | Words kept |
|---|---|
| 1 | 17,286 |
| 5 | 3,852 |
| 20 (notebook) | 918 |
| 50 | 271 |
The Similarity Matrix
from sklearn.metrics.pairwise import cosine_similarity
sim = cosine_similarity(word_matrix)
sim.shape- ▸
(4803, 4803):sim[i, j]is the cosine of moviesiandj - ▸Symmetric:
sim[i, j]equalssim[j, i] - ▸The diagonal is 1.0 for 4,794 movies; the other 9 have no word left, so their row is all 0
Real vectors from word_matrix
Worked Example: Skyfall and Spectre
Every count is 1 in both vectors
| Words from | Skyfall | Spectre | Shared |
|---|---|---|---|
| genres | 3 | 3 | 2: action, adventure |
| keywords | 5 | 6 | 3: spy, secret, agent |
| cast and director names | 7 | 6 | 6 |
| Total | 15 | 15 | 11 |
A = Skyfall, B = Spectre
Skyfall and Spectre: the Cosine
Part 3
Generating Recommendations
get_recommendations, line by line
Find the Movie's Row
def get_recommendations(title, df, sim, count=10):
# Get the row index of the specified title in the DataFrame
index = df.index[df['title'].str.lower() == title.lower()]
# Return an empty list if there is no entry for the specified title
if (len(index) == 0):
return []
# Get the corresponding row in the similarity matrix
similarities = list(enumerate(sim[index[0]]))- ▸The title match ignores case:
'skyfall'works too - ▸An unknown title returns
[]instead of an error - ▸
enumeratepairs each score with its movie number; for Skyfall:(0, 0.1732), (1, 0.1333), (2, 0.7333), ...
Sort, Skip, Return Titles
recommendations = sorted(similarities, key=lambda x: x[1], reverse=True)
top_recs = recommendations[1:count + 1]
titles = []
for i in range(len(top_recs)):
title = df.iloc[top_recs[i][0]]['title']
titles.append(title)
return titlesget_recommendations('Skyfall', df, sim)
Skyfall: the Ten Recommendations

A = Mulan (10 words), B = Shrek (15 words)
Why Does Mulan Get Shrek?
Explore: Any Movie, Any Recommendation
Where It Goes Wrong
- ▸Empty vectors: 9 movies keep no word after
min_df=20, so every score in their row is 0 - ▸Duplicate titles: two movies are called
Batman; the function always uses the first one - ▸Ties with itself: in 14 rows the movie is not entry 0, so
[1:]can skip the wrong movie - ▸Content only: the model never sees ratings, so it cannot tell a good movie from a bad one
One of the 9 empty vectors
Worked Example: Sharkskin
["Pirates of the Caribbean: At World's End", 'Spectre', 'The Dark Knight Rises']Part 4
Project Teams Presentations and Discussions
This week's project milestone
Your team project
Your Team Presentation
- 1The idea, or the research paper, and why it matters
- 2The data: source, exploration and cleaning
- 3The features and the target variable you chose
- 4The model you implemented and how you evaluated it
- 5What you will improve next
Before you practise
Common Mistakes
- ▸Reading
Data/movies.csvin Colab, where the folder does not exist - ▸Forgetting
fillna(''), so missing text breaksCountVectorizer - ▸Returning entry 0: the liked movie recommends itself
- ▸Adding shared words and forgetting to divide by the lengths
- ▸Trusting a list without its scores (empty vectors, ties, duplicates)
- ▸Expecting a rare title word to count when
min_dfhas dropped it
Part 5
Practice: Your Turn
About 30 minutes, answers follow each task
About 10 minutes
Practice 1: A Sixth Movie
- ▸Row 11 of the file, Quantum of Solace, has genres
Adventure Action Thriller Crime - ▸Write its count vector over the same 8 words and compute its length
- ▸Compute its cosine with each of the five movies of the worked example
- ▸Which two movies would you recommend to a viewer who liked it?
Answers
Practice 1: Answer
- ▸A = Quantum of Solace =
[1 1 1 0 0 0 0 1], ‖A‖ = √4 = 2
| Movie B | A · B | cos(A, B) |
|---|---|---|
| Spectre | 3 | 3 / (2 × 1.7321) = 0.8660 |
| The Dark Knight Rises | 3 | 3 / (2 × 2) = 0.7500 |
| Pirates of the Caribbean | 2 | 2 / (2 × 1.7321) = 0.5774 |
| John Carter | 2 | 2 / (2 × 2) = 0.5000 |
| Avatar | 2 | 2 / (2 × 2.2361) = 0.4472 |
About 5 minutes
Practice 2: Counts and Length
- ▸Three short texts: X =
space war space alien, Y =space alien robot, and Z = X written twice - ▸Use the vocabulary
alien, robot, space, warand write X, Y and Z as count vectors - ▸Compute cos(X, Y), cos(Z, Y) and cos(X, Z)
- ▸What does repeating a text do to its cosine?
Answers
Practice 2: Answer
- ▸X =
[1, 0, 2, 1], Y =[1, 1, 1, 0], Z =[2, 0, 4, 2]
About 10 minutes
Practice 3: Scores in Colab
- 1Run the notebook up to
sim = cosine_similarity(word_matrix) - 2Copy
get_recommendationsand make it return(title, score)pairs, with the score rounded to 4 places - 3Print the top 5 for
'The Dark Knight Rises' - 4Check the first score by hand from
word_matrix: the dot product and the two sums of squares
Answers
Practice 3: Answer
| Rank | Movie | Score |
|---|---|---|
| 1 | The Dark Knight | 0.7924 |
| 2 | Batman Begins | 0.7348 |
| 3 | Harsh Times | 0.4667 |
| 4 | The Killer Inside Me | 0.4648 |
| 5 | Amidst the Devil's Wings | 0.4619 |
About 5 minutes
Practice 4: Change min_df
- ▸Shape
(4803, 3852): many more words survive - ▸Spectre is still first, but drops from 0.7333 to 0.5500: each vector now holds 20 words, still 11 shared
- ▸Among the newcomers: Casino Royale (0.3500) and Goldfinger (0.2635)
Key Takeaways
- 1A content-based recommender suggests items whose content is closest to one you liked
- 2
CountVectorizerturns text into count vectors;stop_wordsandmin_dfdecide which words count - 3Cosine similarity is the dot product divided by the two lengths: between 0 and 1 for counts
- 4
cosine_similaritygives the full matrix; one sorted row is a recommendation list - 5Always look at the scores: empty vectors, ties and duplicate titles give misleading lists
Open this lesson
Mahmoud Abas|Content-Based Recommendations