Khóa học
Mục đích của hướng dẫn này không phải để biến bạn thành chuyên gia xây dựng mô hình hệ thống gợi ý. Thay vào đó, mục tiêu là giúp bạn bắt đầu bằng cách cung cấp tổng quan về các kiểu hệ thống gợi ý hiện có và cách bạn có thể tự xây dựng một hệ thống.
Trong hướng dẫn này, bạn sẽ học cách xây dựng mô hình cơ bản của hệ thống gợi ý đơn giản và dựa trên nội dung. Dù những mô hình này còn xa mới đạt chuẩn công nghiệp về độ phức tạp, chất lượng hay độ chính xác, chúng sẽ giúp bạn bắt đầu xây dựng các mô hình phức tạp hơn với kết quả tốt hơn.
Hệ thống gợi ý là một trong những ứng dụng phổ biến nhất của khoa học dữ liệu hiện nay. Chúng được dùng để dự đoán "điểm đánh giá" hoặc "sở thích" mà một người dùng sẽ dành cho một mục. Hầu như mọi công ty công nghệ lớn đều áp dụng chúng ở một mức độ nào đó. Amazon dùng để gợi ý sản phẩm cho khách hàng, YouTube dùng để quyết định video tự động phát tiếp theo, và Facebook dùng để gợi ý các trang nên thích và người nên theo dõi.
Hơn nữa, với một số công ty như Netflix, Amazon Prime, Hulu và Hotstar, mô hình kinh doanh và thành công của họ xoay quanh sức mạnh của hệ thống gợi ý. Năm 2009, Netflix thậm chí treo thưởng một triệu đô la cho bất kỳ ai có thể cải thiện hệ thống của họ thêm 10%.
Cũng có các hệ thống gợi ý phổ biến cho các lĩnh vực như nhà hàng, phim ảnh, và hẹn hò trực tuyến. Hệ thống gợi ý cũng được phát triển để khám phá bài báo nghiên cứu và chuyên gia, cộng tác viên, và dịch vụ tài chính. YouTube sử dụng hệ thống gợi ý ở quy mô lớn để đề xuất video dựa trên lịch sử của bạn. Ví dụ, nếu bạn xem nhiều video giáo dục, nó sẽ gợi ý các video thuộc kiểu đó.
Vậy hệ thống gợi ý là gì?
Nhìn chung, hệ thống gợi ý có thể được phân loại thành 3 loại:
- Trình gợi ý đơn giản: cung cấp gợi ý chung cho mọi người dùng, dựa trên độ phổ biến và/hoặc thể loại phim. Ý tưởng cơ bản là những bộ phim phổ biến và được giới phê bình đánh giá cao sẽ có xác suất được khán giả đại chúng yêu thích cao hơn. Ví dụ có thể là IMDB Top 250.
- Trình gợi ý dựa trên nội dung: đề xuất các mục tương tự dựa trên một mục cụ thể. Hệ thống này dùng siêu dữ liệu của mục, như thể loại, đạo diễn, mô tả, diễn viên, v.v. đối với phim, để đưa ra gợi ý. Ý tưởng chung là nếu một người thích một mục nào đó, họ cũng sẽ thích mục tương tự. Để gợi ý như vậy, hệ thống sẽ tận dụng siêu dữ liệu các mục mà người dùng đã tương tác trước đây. Ví dụ điển hình là YouTube, nơi dựa trên lịch sử của bạn, nó đề xuất các video mới mà bạn có thể muốn xem.
- Động cơ lọc cộng tác: các hệ thống này được sử dụng rộng rãi, cố gắng dự đoán điểm đánh giá hoặc sở thích mà một người dùng sẽ dành cho một mục dựa trên các đánh giá và sở thích trước đó của những người dùng khác. Lọc cộng tác không yêu cầu siêu dữ liệu của mục như các hệ thống dựa trên nội dung.
Trình gợi ý đơn giản
Như đã mô tả ở phần trước, trình gợi ý đơn giản là hệ thống cơ bản gợi ý các mục hàng đầu dựa trên một số chỉ số hoặc điểm. Trong phần này, bạn sẽ xây dựng một bản sao đơn giản của IMDB Top 250 Movies sử dụng siêu dữ liệu thu thập từ IMDB.
Các bước thực hiện như sau:
-
Quyết định chỉ số hoặc điểm để xếp hạng phim.
-
Tính điểm cho mỗi bộ phim.
-
Sắp xếp phim theo điểm số và xuất ra kết quả hàng đầu.
Về bộ dữ liệu
Các tệp dữ liệu chứa siêu dữ liệu cho toàn bộ 45.000 phim có trong Full MovieLens Dataset. Bộ dữ liệu gồm các phim phát hành vào hoặc trước tháng 7/2017. Bộ dữ liệu này ghi nhận các đặc trưng như dàn diễn viên, ê-kíp, từ khóa cốt truyện, ngân sách, doanh thu, poster, ngày phát hành, ngôn ngữ, hãng sản xuất, quốc gia, số phiếu bình chọn trên TMDB và điểm bình chọn trung bình.
Các đặc trưng này có thể được dùng để huấn luyện mô hình máy học cho gợi ý dựa trên nội dung và lọc cộng tác.
Bộ dữ liệu này gồm các tệp sau:
- movies_metadata.csv: Tệp này chứa thông tin về khoảng 45.000 phim có trong bộ dữ liệu Full MovieLens. Đặc trưng gồm poster, ảnh nền, ngân sách, thể loại, doanh thu, ngày phát hành, ngôn ngữ, quốc gia và hãng sản xuất.
- keywords.csv: Chứa các từ khóa cốt truyện cho các phim trong MovieLens. Có dạng Đối tượng JSON dưới dạng chuỗi.
- credits.csv: Gồm thông tin dàn diễn viên và ê-kíp cho tất cả các phim. Có dạng Đối tượng JSON dưới dạng chuỗi.
- links.csv: Tệp này chứa ID TMDB và IMDB của tất cả phim trong bộ dữ liệu Full MovieLens.
- links_small.csv: Chứa ID TMDB và IMDB của một tập con nhỏ gồm 9.000 phim từ bộ đầy đủ.
- ratings_small.csv: Tập con gồm 100.000 lượt đánh giá từ 700 người dùng cho 9.000 phim.
Bộ dữ liệu Full MovieLens bao gồm 26 triệu lượt đánh giá và 750.000 lượt gán thẻ, từ 270.000 người dùng trên toàn bộ 45.000 phim trong bộ này. Có thể truy cập từ trang GroupLens chính thức.
Lưu ý: Tập con dữ liệu dùng trong hướng dẫn hôm nay có thể tải về từ đây.
Để tải bộ dữ liệu, bạn sẽ dùng thư viện DataFrame pandas. Thư viện pandas chủ yếu dùng để xử lý và phân tích dữ liệu. Nó biểu diễn dữ liệu theo dạng hàng-cột. Thư viện Pandas được xây dựng trên mảng NumPy để hiện thực các đối tượng dữ liệu pandas. pandas cung cấp sẵn cấu trúc dữ liệu và thao tác để xử lý bảng số liệu, chuỗi thời gian, ảnh, và dữ liệu xử lý ngôn ngữ tự nhiên. Nói chung, pandas hữu ích cho các bộ dữ liệu có thể biểu diễn dễ dàng dưới dạng bảng.
Trước khi thực hiện các bước trên, hãy nạp siêu dữ liệu phim của bạn vào một DataFrame pandas:
# Import Pandas
import pandas as pd
# Load Movies Metadata
metadata = pd.read_csv('movies_metadata.csv', low_memory=False)
# Print the first three rows
metadata.head(3)
| adult | belongs_to_collection | budget | genres | homepage | id | imdb_id | original_language | original_title | overview | ... | release_date | revenue | runtime | spoken_languages | status | tagline | title | video | vote_average | vote_count | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | False | {'id': 10194, 'name': 'Toy Story Collection', ... | 30000000 | [{'id': 16, 'name': 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Led by Woody, Andy's toys live happily in his ... | ... | 1995-10-30 | 373554033.0 | 81.0 | [{'iso_639_1': 'en', 'name': 'English'}] | Released | NaN | Toy Story | False | 7.7 | 5415.0 |
| 1 | False | NaN | 65000000 | [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | When siblings Judy and Peter discover an encha... | ... | 1995-12-15 | 262797249.0 | 104.0 | [{'iso_639_1': 'en', 'name': 'English'}, {'iso... | Released | Roll the dice and unleash the excitement! | Jumanji | False | 6.9 | 2413.0 |
| 2 | False | {'id': 119050, 'name': 'Grumpy Old Men Collect... | 0 | [{'id': 10749, 'name': 'Romance'}, {'id': 35, ... | NaN | 15602 | tt0113228 | en | Grumpier Old Men | A family wedding reignites the ancient feud be... | ... | 1995-12-22 | 0.0 | 101.0 | [{'iso_639_1': 'en', 'name': 'English'}] | Released | Still Yelling. Still Fighting. Still Ready for... | Grumpier Old Men | False | 6.5 | 92.0 |
3 hàng × 24 cột
Một trong những chỉ số cơ bản nhất bạn có thể nghĩ đến là xếp hạng để quyết định top 250 phim dựa trên điểm đánh giá tương ứng.
Tuy nhiên, dùng điểm đánh giá làm chỉ số có vài điểm cần lưu ý:
-
Thứ nhất, nó không xét đến độ phổ biến của phim. Do đó, một phim có điểm 9 từ 10 người bình chọn sẽ bị coi là 'tốt hơn' một phim có điểm 8,9 từ 10.000 người bình chọn.
Ví dụ, tưởng tượng bạn muốn gọi đồ ăn Trung Quốc, bạn có vài lựa chọn: một nhà hàng được 5 sao bởi chỉ 5 người, trong khi nhà hàng khác được 4,5 sao bởi 1000 người. Bạn sẽ chọn nhà hàng nào? Cái thứ hai, đúng không?
Tất nhiên, có thể có ngoại lệ là nhà hàng thứ nhất mới mở vài ngày nên ít người đánh giá, trong khi nhà hàng thứ hai đã hoạt động một năm.
- Liên quan đến đó, chỉ số này cũng có xu hướng ưu ái những phim có ít người bình chọn với điểm lệch hoặc cực kỳ cao. Khi số người bình chọn tăng, điểm phim sẽ điều hòa và tiến gần về giá trị phản ánh chất lượng thực của phim, giúp người dùng có hình dung tốt hơn nên chọn phim nào. Trong khi khó đánh giá chất lượng phim khi có quá ít bình chọn, bạn có thể phải tham khảo nguồn bên ngoài để kết luận.
Xét đến các hạn chế này, bạn cần đưa ra một điểm đánh giá có trọng số, tính đến cả điểm trung bình và số phiếu tích lũy. Hệ thống như vậy sẽ đảm bảo một phim đạt điểm 9 từ 100.000 người bình chọn sẽ có điểm (rất) cao hơn một phim cùng điểm nhưng chỉ vài trăm người bình chọn.
Vì bạn đang cố xây một bản sao của IMDB Top 250, hãy dùng công thức điểm có trọng số của IMDB làm chỉ số/điểm. Về mặt toán học, được biểu diễn như sau:
\begin{equation} \text Weighted Rating (\bf WR) = \left({{\bf v} \over {\bf v} + {\bf m}} \cdot R\right) + \left({{\bf m} \over {\bf v} + {\bf m}} \cdot C\right) \end{equation}
Trong phương trình trên,
-
v là số phiếu bình chọn cho phim;
-
m là số phiếu tối thiểu để được liệt kê trong bảng xếp hạng;
-
R là điểm trung bình của phim;
-
C là điểm trung bình trên toàn bộ báo cáo.
Bạn đã có sẵn giá trị v (vote_count) và R (vote_average) cho mỗi phim trong bộ dữ liệu. Bạn cũng có thể trực tiếp tính C từ dữ liệu này.
Xác định giá trị phù hợp cho m là một siêu tham số bạn có thể tùy chọn, vì không có giá trị đúng duy nhất cho m. Bạn có thể coi nó là một bộ lọc âm ban đầu sẽ loại bỏ các phim có số phiếu ít hơn một ngưỡng m nhất định. Mức độ chọn lọc là tùy bạn.
Trong hướng dẫn này, bạn sẽ dùng ngưỡng m là phân vị 90. Nói cách khác, để một phim xuất hiện trong bảng xếp hạng, nó phải có số phiếu nhiều hơn ít nhất 90% số phim trong danh sách. (Ngược lại, nếu bạn chọn phân vị 75, bạn sẽ xét top 25% phim theo số phiếu. Khi phân vị giảm, số phim được xét sẽ tăng).
Bước đầu tiên, hãy tính giá trị C, điểm trung bình trên tất cả phim bằng hàm .mean() của pandas:
# Calculate mean of vote average column
C = metadata['vote_average'].mean()
print(C)
5.618207215133889
Từ kết quả trên, bạn có thể thấy điểm trung bình của một phim trên IMDB vào khoảng 5,6 theo thang 10.
Tiếp theo, hãy tính số phiếu m mà một phim nhận được ở phân vị 90. Thư viện pandas giúp tác vụ này cực kỳ đơn giản với phương thức .quantile():
# Calculate the minimum number of votes required to be in the chart, m
m = metadata['vote_count'].quantile(0.90)
print(m)
160.0
Vì giờ bạn đã có m, bạn có thể dùng điều kiện lớn hơn hoặc bằng để lọc ra các phim có số phiếu lớn hơn hoặc bằng 160:
Bạn có thể dùng phương thức .copy() để đảm bảo DataFrame q_movies mới được tạo là độc lập với DataFrame metadata gốc. Nói cách khác, mọi thay đổi với q_movies sẽ không ảnh hưởng đến metadata gốc.
# Filter out all qualified movies into a new DataFrame
q_movies = metadata.copy().loc[metadata['vote_count'] >= m]
q_movies.shape
(4555, 24)
metadata.shape
(45466, 24)
Từ kết quả trên, rõ ràng khoảng 10% phim có số phiếu nhiều hơn 160 và đủ điều kiện vào danh sách này.
Tiếp theo và quan trọng nhất là tính điểm có trọng số cho mỗi phim đủ điều kiện. Để làm điều này, bạn sẽ:
- Định nghĩa một hàm
weighted_rating(); - Vì bạn đã tính
mvàCnên sẽ truyền chúng làm tham số cho hàm; - Sau đó chọn cột
vote_count(v) vàvote_average(R) từ DataFrameq_movies; - Cuối cùng, tính điểm trung bình có trọng số và trả về kết quả.
Bạn sẽ định nghĩa một đặc trưng mới score, tính giá trị của nó bằng cách áp dụng hàm này lên DataFrame các phim đủ điều kiện:
# Function that computes the weighted rating of each movie
def weighted_rating(x, m=m, C=C):
v = x['vote_count']
R = x['vote_average']
# Calculation based on the IMDB formula
return (v/(v+m) * R) + (m/(m+v) * C)
# Define a new feature 'score' and calculate its value with `weighted_rating()`
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)
Cuối cùng, hãy sắp xếp DataFrame theo thứ tự giảm dần dựa trên cột đặc trưng score và xuất tiêu đề, số phiếu, điểm trung bình và điểm có trọng số (score) của 20 phim hàng đầu.
#Sort movies based on score calculated above
q_movies = q_movies.sort_values('score', ascending=False)
#Print the top 15 movies
q_movies[['title', 'vote_count', 'vote_average', 'score']].head(20)
| title | vote_count | vote_average | score | |
|---|---|---|---|---|
| 314 | The Shawshank Redemption | 8358.0 | 8.5 | 8.445869 |
| 834 | The Godfather | 6024.0 | 8.5 | 8.425439 |
| 10309 | Dilwale Dulhania Le Jayenge | 661.0 | 9.1 | 8.421453 |
| 12481 | The Dark Knight | 12269.0 | 8.3 | 8.265477 |
| 2843 | Fight Club | 9678.0 | 8.3 | 8.256385 |
| 292 | Pulp Fiction | 8670.0 | 8.3 | 8.251406 |
| 522 | Schindler's List | 4436.0 | 8.3 | 8.206639 |
| 23673 | Whiplash | 4376.0 | 8.3 | 8.205404 |
| 5481 | Spirited Away | 3968.0 | 8.3 | 8.196055 |
| 2211 | Life Is Beautiful | 3643.0 | 8.3 | 8.187171 |
| 1178 | The Godfather: Part II | 3418.0 | 8.3 | 8.180076 |
| 1152 | One Flew Over the Cuckoo's Nest | 3001.0 | 8.3 | 8.164256 |
| 351 | Forrest Gump | 8147.0 | 8.2 | 8.150272 |
| 1154 | The Empire Strikes Back | 5998.0 | 8.2 | 8.132919 |
| 1176 | Psycho | 2405.0 | 8.3 | 8.132715 |
| 18465 | The Intouchables | 5410.0 | 8.2 | 8.125837 |
| 40251 | Your Name. | 1030.0 | 8.5 | 8.112532 |
| 289 | Leon: The Professional | 4293.0 | 8.2 | 8.107234 |
| 3030 | The Green Mile | 4166.0 | 8.2 | 8.104511 |
| 1170 | GoodFellas | 3211.0 | 8.2 | 8.077459 |
Từ kết quả trên, bạn có thể thấy trình gợi ý đơn giản đã làm rất tốt!
Bởi vì bảng xếp hạng có nhiều phim trùng với IMDB Top 250: ví dụ, hai phim đứng đầu của bạn, "Shawshank Redemption" và "The Godfather", giống với IMDB và chúng ta đều biết đó là những phim tuyệt vời; thực tế, toàn bộ 20 phim hàng đầu đều xứng đáng có trong danh sách ấy, phải không?
Trình gợi ý dựa trên nội dung
Trình gợi ý dựa trên mô tả cốt truyện
Trong phần này, bạn sẽ học cách xây dựng hệ thống gợi ý các phim tương tự với một phim cụ thể. Để đạt được điều này, bạn sẽ tính điểm tương đồng cosine theo cặp cho tất cả phim dựa trên mô tả cốt truyện và gợi ý phim dựa trên ngưỡng điểm tương đồng đó.
Mô tả cốt truyện có sẵn dưới dạng đặc trưng overview trong bộ dữ liệu metadata. Hãy xem qua cốt truyện của vài phim:
#Print plot overviews of the first 5 movies.
metadata['overview'].head()
0 Led by Woody, Andy's toys live happily in his ...
1 When siblings Judy and Peter discover an encha...
2 A family wedding reignites the ancient feud be...
3 Cheated on, mistreated and stepped on, the wom...
4 Just when George Banks has recovered from his ...
Name: overview, dtype: object
Bài toán ở đây là một bài toán Xử lý Ngôn ngữ Tự nhiên. Vì vậy, bạn cần trích xuất một số loại đặc trưng từ dữ liệu văn bản trên trước khi có thể tính độ tương đồng/khác biệt giữa chúng. Nói đơn giản, không thể tính độ tương đồng giữa hai mục overview ở dạng thô. Để làm điều này, bạn cần tính các vector từ cho mỗi overview hay tài liệu, như ta sẽ gọi từ bây giờ.
Đúng như tên gọi, vector từ là biểu diễn dạng vector của các từ trong một tài liệu. Các vector này mang ý nghĩa ngữ nghĩa. Ví dụ, man & king sẽ có biểu diễn vector gần nhau trong khi man & woman sẽ có biểu diễn cách xa nhau.
Bạn sẽ tính vector Term Frequency-Inverse Document Frequency (TF-IDF) cho mỗi tài liệu. Việc này cho bạn một ma trận trong đó mỗi cột đại diện cho một từ trong từ vựng của overview (tất cả các từ xuất hiện trong ít nhất một tài liệu), và mỗi cột đại diện cho một bộ phim, như trước.
Về bản chất, điểm TF-IDF là tần suất một từ xuất hiện trong tài liệu, được giảm trọng số bởi số tài liệu mà nó xuất hiện. Điều này nhằm giảm tầm quan trọng của những từ thường xuyên xuất hiện trong mô tả cốt truyện và do đó giảm ảnh hưởng của chúng khi tính điểm tương đồng cuối cùng.
May mắn thay, scikit-learn có sẵn lớp TfIdfVectorizer tạo ra ma trận TF-IDF chỉ trong vài dòng.
- Nhập mô-đun Tfidf từ scikit-learn;
- Loại bỏ từ dừng như 'the', 'an', v.v. vì chúng không cung cấp thông tin hữu ích về chủ đề;
- Thay thế giá trị không-phải-số bằng chuỗi trống;
- Cuối cùng, xây dựng ma trận TF-IDF trên dữ liệu.
#Import TfIdfVectorizer from scikit-learn
from sklearn.feature_extraction.text import TfidfVectorizer
#Define a TF-IDF Vectorizer Object. Remove all english stop words such as 'the', 'a'
tfidf = TfidfVectorizer(stop_words='english')
#Replace NaN with an empty string
metadata['overview'] = metadata['overview'].fillna('')
#Construct the required TF-IDF matrix by fitting and transforming the data
tfidf_matrix = tfidf.fit_transform(metadata['overview'])
#Output the shape of tfidf_matrix
tfidf_matrix.shape
(45466, 75827)
#Array mapping from feature integer indices to feature name.
tfidf.get_feature_names()[5000:5010]
['avails',
'avaks',
'avalanche',
'avalanches',
'avallone',
'avalon',
'avant',
'avanthika',
'avanti',
'avaracious']
Từ kết quả trên, bạn thấy rằng có 75.827 từ vựng khác nhau trong bộ dữ liệu của bạn với 45.000 phim.
Với ma trận này trong tay, giờ bạn có thể tính điểm tương đồng. Có nhiều thước đo tương đồng có thể dùng, như manhattan, euclid, Pearson, và cosine. Một lần nữa, không có câu trả lời đúng tuyệt đối cho thước đo nào tốt nhất. Các thước đo khác nhau hoạt động tốt trong những tình huống khác nhau, và thường nên thử nghiệm với các thước đo khác nhau và quan sát kết quả.
Bạn sẽ dùng cosine similarity để tính một đại lượng số biểu thị mức độ tương đồng giữa hai bộ phim. Bạn dùng cosine vì nó độc lập với độ lớn và tương đối dễ và nhanh để tính (đặc biệt khi kết hợp với điểm TF-IDF, sẽ được giải thích sau). Về mặt toán học, định nghĩa như sau:

Vì bạn đã dùng bộ biến đổi TF-IDF, tính tích vô hướng giữa các vector sẽ cho trực tiếp điểm tương đồng cosine. Do đó, bạn sẽ dùng linear_kernel() của sklearn thay vì cosine_similarities() vì nó nhanh hơn.
Điều này sẽ trả về ma trận kích thước 45466x45466, nghĩa là điểm tương đồng cosine giữa overview của mỗi phim với mọi phim khác. Như vậy, mỗi phim sẽ là một vector cột 1x45466, trong đó mỗi cột là điểm tương đồng với từng phim.
# Import linear_kernel
from sklearn.metrics.pairwise import linear_kernel
# Compute the cosine similarity matrix
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
cosine_sim.shape
(45466, 45466)
cosine_sim[1]
array([0.01504121, 1. , 0.04681953, ..., 0. , 0.02198641,
0.00929411])
Bạn sẽ định nghĩa một hàm nhận vào tiêu đề phim và xuất ra danh sách 10 phim giống nhất. Trước hết, để làm điều này, bạn cần một ánh xạ ngược giữa tiêu đề phim và chỉ số của DataFrame. Nói cách khác, bạn cần cơ chế xác định chỉ số của một phim trong DataFrame metadata khi biết tiêu đề.
#Construct a reverse map of indices and movie titles
indices = pd.Series(metadata.index, index=metadata['title']).drop_duplicates()
indices[:10]
title
Toy Story 0
Jumanji 1
Grumpier Old Men 2
Waiting to Exhale 3
Father of the Bride Part II 4
Heat 5
Sabrina 6
Tom and Huck 7
Sudden Death 8
GoldenEye 9
dtype: int64
Giờ bạn đã sẵn sàng để định nghĩa hàm gợi ý. Bạn sẽ làm theo các bước sau:
-
Lấy chỉ số của phim dựa trên tiêu đề.
-
Lấy danh sách điểm tương đồng cosine cho phim đó với tất cả phim. Chuyển nó thành danh sách các bộ gồm vị trí và điểm tương đồng.
-
Sắp xếp danh sách bộ nói trên dựa trên điểm tương đồng; tức là phần tử thứ hai.
-
Lấy 10 phần tử đứng đầu danh sách. Bỏ qua phần tử đầu tiên vì đó là chính nó (phim giống nhất với một phim là bản thân phim đó).
-
Trả về tiêu đề tương ứng với các chỉ số của các phần tử đứng đầu.
# Function that takes in movie title as input and outputs most similar movies
def get_recommendations(title, cosine_sim=cosine_sim):
# Get the index of the movie that matches the title
idx = indices[title]
# Get the pairwsie similarity scores of all movies with that movie
sim_scores = list(enumerate(cosine_sim[idx]))
# Sort the movies based on the similarity scores
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
# Get the scores of the 10 most similar movies
sim_scores = sim_scores[1:11]
# Get the movie indices
movie_indices = [i[0] for i in sim_scores]
# Return the top 10 most similar movies
return metadata['title'].iloc[movie_indices]
get_recommendations('The Dark Knight Rises')
12481 The Dark Knight
150 Batman Forever
1328 Batman Returns
15511 Batman: Under the Red Hood
585 Batman
21194 Batman Unmasked: The Psychology of the Dark Kn...
9230 Batman Beyond: Return of the Joker
18035 Batman: Year One
19792 Batman: The Dark Knight Returns, Part 1
3095 Batman: Mask of the Phantasm
Name: title, dtype: object
get_recommendations('The Godfather')
1178 The Godfather: Part II
44030 The Godfather Trilogy: 1972-1990
1914 The Godfather: Part III
23126 Blood Ties
11297 Household Saints
34717 Start Liquidation
10821 Election
38030 A Mother Should Be Loved
17729 Short Sharp Shock
26293 Beck 28 - Familjen
Name: title, dtype: object
Bạn thấy rằng, dù hệ thống của bạn đã làm khá ổn khi tìm các phim có mô tả cốt truyện tương tự, chất lượng gợi ý vẫn chưa thật sự tốt. "The Dark Knight Rises" trả về toàn phim Batman trong khi khả năng cao là những người thích phim đó cũng sẽ thích các phim khác của Christopher Nolan. Điều này không thể được hệ thống hiện tại nắm bắt.
Trình gợi ý dựa trên diễn viên, thể loại và từ khóa
Chất lượng của hệ thống gợi ý sẽ được nâng cao khi dùng siêu dữ liệu tốt hơn và thu nhận nhiều chi tiết tinh tế hơn. Đó chính là điều bạn sẽ làm trong phần này. Bạn sẽ xây dựng hệ thống gợi ý dựa trên các siêu dữ liệu sau: 3 diễn viên hàng đầu, đạo diễn, các thể loại liên quan, và từ khóa cốt truyện.
Dữ liệu từ khóa, dàn diễn viên và ê-kíp chưa có trong bộ dữ liệu hiện tại, vì vậy bước đầu tiên là tải và gộp chúng vào DataFrame chính metadata.
# Load keywords and credits
credits = pd.read_csv('credits.csv')
keywords = pd.read_csv('keywords.csv')
# Remove rows with bad IDs.
metadata = metadata.drop([19730, 29503, 35587])
# Convert IDs to int. Required for merging
keywords['id'] = keywords['id'].astype('int')
credits['id'] = credits['id'].astype('int')
metadata['id'] = metadata['id'].astype('int')
# Merge keywords and credits into your main metadata dataframe
metadata = metadata.merge(credits, on='id')
metadata = metadata.merge(keywords, on='id')
# Print the first two movies of your newly merged metadata
metadata.head(2)
| adult | belongs_to_collection | budget | genres | homepage | id | imdb_id | original_language | original_title | overview | ... | spoken_languages | status | tagline | title | video | vote_average | vote_count | cast | crew | keywords | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | False | {'id': 10194, 'name': 'Toy Story Collection', ... | 30000000 | [{'id': 16, 'name': 'Animation'}, {'id': 35, '... | http://toystory.disney.com/toy-story | 862 | tt0114709 | en | Toy Story | Led by Woody, Andy's toys live happily in his ... | ... | [{'iso_639_1': 'en', 'name': 'English'}] | Released | NaN | Toy Story | False | 7.7 | 5415.0 | [{'cast_id': 14, 'character': 'Woody (voice)',... | [{'credit_id': '52fe4284c3a36847f8024f49', 'de... | [{'id': 931, 'name': 'jealousy'}, {'id': 4290,... |
| 1 | False | NaN | 65000000 | [{'id': 12, 'name': 'Adventure'}, {'id': 14, '... | NaN | 8844 | tt0113497 | en | Jumanji | When siblings Judy and Peter discover an encha... | ... | [{'iso_639_1': 'en', 'name': 'English'}, {'iso... | Released | Roll the dice and unleash the excitement! | Jumanji | False | 6.9 | 2413.0 | [{'cast_id': 1, 'character': 'Alan Parrish', '... | [{'credit_id': '52fe44bfc3a36847f80a7cd1', 'de... | [{'id': 10090, 'name': 'board game'}, {'id': 1... |
2 hàng × 27 cột
Từ các đặc trưng mới, cast, crew và keywords, bạn cần trích xuất ba diễn viên quan trọng nhất, đạo diễn và các từ khóa gắn với phim đó.
Nhưng trước hết, dữ liệu của bạn đang ở dạng danh sách "chuỗi hóa". Bạn cần chuyển chúng thành dạng có thể sử dụng.
# Parse the stringified features into their corresponding python objects
from ast import literal_eval
features = ['cast', 'crew', 'keywords', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(literal_eval)
Tiếp theo, bạn viết các hàm giúp trích xuất thông tin cần thiết từ mỗi đặc trưng.
Đầu tiên, bạn sẽ import gói NumPy để dùng hằng NaN của nó. Kế đến, bạn có thể dùng nó để viết hàm get_director():
# Import Numpy
import numpy as np
Lấy tên đạo diễn từ đặc trưng crew. Nếu không liệt kê đạo diễn, trả về NaN
def get_director(x):
for i in x:
if i['job'] == 'Director':
return i['name']
return np.nan
Tiếp theo, bạn sẽ viết một hàm trả về 3 phần tử đầu hoặc toàn bộ danh sách, tùy danh sách dài bao nhiêu. Ở đây, danh sách đề cập đến cast, keywords và genres.
def get_list(x):
if isinstance(x, list):
names = [i['name'] for i in x]
#Check if more than 3 elements exist. If yes, return only first three. If no, return entire list.
if len(names) > 3:
names = names[:3]
return names
#Return empty list in case of missing/malformed data
return []
# Define new director, cast, genres and keywords features that are in a suitable form.
metadata['director'] = metadata['crew'].apply(get_director)
features = ['cast', 'keywords', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(get_list)
# Print the new features of the first 3 films
metadata[['title', 'cast', 'director', 'keywords', 'genres']].head(3)
| title | cast | director | keywords | genres | |
|---|---|---|---|---|---|
| 0 | Toy Story | [Tom Hanks, Tim Allen, Don Rickles] | John Lasseter | [jealousy, toy, boy] | [Animation, Comedy, Family] |
| 1 | Jumanji | [Robin Williams, Jonathan Hyde, Kirsten Dunst] | Joe Johnston | [board game, disappearance, based on children'... | [Adventure, Fantasy, Family] |
| 2 | Grumpier Old Men | [Walter Matthau, Jack Lemmon, Ann-Margret] | Howard Deutch | [fishing, best friend, duringcreditsstinger] | [Romance, Comedy] |
Bước tiếp theo là chuyển tên và các từ khóa về chữ thường và loại bỏ toàn bộ khoảng trắng giữa chúng.
Loại bỏ khoảng trắng giữa các từ là một bước tiền xử lý quan trọng. Làm vậy để bộ vectorizer của bạn không đếm Johnny trong "Johnny Depp" và "Johnny Galecki" là giống nhau. Sau bước này, các diễn viên trên sẽ được biểu diễn thành "johnnydepp" và "johnnygalecki" và được coi là khác nhau bởi vectorizer.
Một ví dụ khác nơi mô hình có thể cho cùng biểu diễn vector là "bread jam" và "traffic jam". Do đó, tốt hơn là loại bỏ mọi khoảng trắng hiện có.
Hàm dưới đây sẽ làm chính xác điều đó cho bạn:
# Function to convert all strings to lower case and strip names of spaces
def clean_data(x):
if isinstance(x, list):
return [str.lower(i.replace(" ", "")) for i in x]
else:
#Check if director exists. If not, return empty string
if isinstance(x, str):
return str.lower(x.replace(" ", ""))
else:
return ''
# Apply clean_data function to your features.
features = ['cast', 'keywords', 'director', 'genres']
for feature in features:
metadata[feature] = metadata[feature].apply(clean_data)
Giờ bạn đã sẵn sàng tạo "metadata soup", tức một chuỗi chứa toàn bộ siêu dữ liệu bạn muốn đưa vào vectorizer (cụ thể là diễn viên, đạo diễn và từ khóa).
Hàm create_soup đơn giản sẽ nối tất cả các cột cần thiết bằng một khoảng trắng. Đây là bước tiền xử lý cuối cùng, và đầu ra của hàm sẽ được đưa vào mô hình vector từ.
def create_soup(x):
return ' '.join(x['keywords']) + ' ' + ' '.join(x['cast']) + ' ' + x['director'] + ' ' + ' '.join(x['genres'])
# Create a new soup feature
metadata['soup'] = metadata.apply(create_soup, axis=1)
metadata[['soup']].head(2)
| soup | |
|---|---|
| 0 | jealousy toy boy tomhanks timallen donrickles ... |
| 1 | boardgame disappearance basedonchildren'sbook ... |
Các bước tiếp theo giống như bạn đã làm với trình gợi ý dựa trên mô tả cốt truyện. Một khác biệt chính là bạn dùng CountVectorizer() thay vì TF-IDF. Lý do là bạn không muốn giảm trọng số sự xuất hiện của diễn viên/đạo diễn nếu họ đóng hay đạo diễn nhiều phim hơn tương đối. Trong ngữ cảnh này, việc giảm trọng số như vậy không hợp lý.
Khác biệt lớn giữa CountVectorizer() và TF-IDF là thành phần tần suất tài liệu nghịch đảo (IDF) có trong cái sau nhưng không có trong cái trước.
# Import CountVectorizer and create the count matrix
from sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(metadata['soup'])
count_matrix.shape
(46628, 73881)
Từ kết quả trên, bạn có thể thấy có 73.881 từ vựng trong siêu dữ liệu bạn đã đưa vào.
Tiếp theo, bạn sẽ dùng cosine_similarity để đo khoảng cách giữa các embedding.
# Compute the Cosine Similarity matrix based on the count_matrix
from sklearn.metrics.pairwise import cosine_similarity
cosine_sim2 = cosine_similarity(count_matrix, count_matrix)
# Reset index of your main DataFrame and construct reverse mapping as before
metadata = metadata.reset_index()
indices = pd.Series(metadata.index, index=metadata['title'])
Giờ bạn có thể tái sử dụng hàm get_recommendations() bằng cách truyền ma trận cosine_sim2 mới làm tham số thứ hai.
get_recommendations('The Dark Knight Rises', cosine_sim2)
12589 The Dark Knight
10210 Batman Begins
9311 Shiner
9874 Amongst Friends
7772 Mitchell
516 Romeo Is Bleeding
11463 The Prestige
24090 Quicksand
25038 Deadfall
41063 Sara
Name: title, dtype: object
get_recommendations('The Godfather', cosine_sim2)
1934 The Godfather: Part III
1199 The Godfather: Part II
15609 The Rain People
18940 Last Exit
34488 Rege
35802 Manuscripts Don't Burn
35803 Manuscripts Don't Burn
8001 The Night of the Following Day
18261 The Son of No One
28683 In the Name of the Law
Name: title, dtype: object
Tuyệt vời! Bạn thấy hệ thống gợi ý của bạn đã nắm bắt được nhiều thông tin hơn nhờ siêu dữ liệu phong phú hơn và đưa ra gợi ý tốt hơn. Tất nhiên, có vô số cách để thử nghiệm hệ thống này nhằm cải thiện chất lượng gợi ý.
Một vài gợi ý:
-
Thêm bộ lọc độ phổ biến: hệ thống gợi ý sẽ lấy 30 phim giống nhất, tính điểm có trọng số (dùng công thức IMDB ở trên), sắp xếp theo điểm và trả về 10 phim hàng đầu.
-
Thành viên ê-kíp khác: có thể bao gồm tên các thành viên ê-kíp khác như biên kịch và nhà sản xuất.
-
Tăng trọng số cho đạo diễn: để tăng trọng số cho đạo diễn, có thể lặp lại tên họ nhiều lần trong soup nhằm tăng điểm tương đồng của các phim cùng đạo diễn.
Lọc cộng tác với Python
Trong hướng dẫn này, bạn đã học cách xây dựng hệ thống gợi ý Phim Đơn giản và Dựa trên Nội dung của riêng bạn. Cũng có một loại hệ thống gợi ý cực kỳ phổ biến khác gọi là lọc cộng tác.
Lọc cộng tác có thể được chia thành hai loại:
User-based Filtering: các hệ thống này gợi ý sản phẩm cho một người dùng mà những người dùng tương tự đã thích. Ví dụ, giả sử Alice và Bob có sở thích sách giống nhau (tức là họ phần lớn thích và không thích cùng các cuốn). Giờ giả sử một cuốn sách mới ra mắt và Alice đã đọc và rất thích nó. Khi đó, khả năng cao Bob cũng sẽ thích, vì vậy hệ thống gợi ý cuốn này cho Bob.
Item-based Filtering: các hệ thống này rất giống với engine gợi ý dựa trên nội dung mà bạn đã xây dựng. Các hệ thống này xác định các mục tương tự dựa trên cách mọi người đã đánh giá chúng trong quá khứ. Ví dụ, nếu Alice, Bob và Eve cùng cho 5 sao The Lord of the Rings và The Hobbit, hệ thống nhận diện các mục này là tương tự. Do đó, nếu ai đó mua The Lord of the Rings, hệ thống cũng gợi ý The Hobbit cho người đó.
Ví dụ về lọc cộng tác dựa trên hệ thống đánh giá:

Bạn sẽ không xây dựng các hệ thống này trong hướng dẫn này, nhưng bạn đã quen với phần lớn ý tưởng cần thiết để làm vậy. Điểm khởi đầu tốt với lọc cộng tác là xem xét bộ dữ liệu MovieLens, có tại đây.
Kết luận
Chúc mừng bạn đã hoàn thành hướng dẫn!
Bạn đã đi qua hướng dẫn của chúng tôi về hệ thống gợi ý bằng Python. Bạn đã học cách xây dựng trình gợi ý đơn giản và dựa trên nội dung.
Một bài tập tốt cho tất cả các bạn là triển khai lọc cộng tác bằng Python sử dụng tập con bộ dữ liệu MovieLens mà bạn đã dùng để xây dựng trình gợi ý đơn giản và dựa trên nội dung.
Nếu bạn mới bắt đầu với Python và muốn học thêm, hãy học khóa Giới thiệu Khoa học Dữ liệu với Python của DataCamp.