Hệ Thống Gợi Ý Khoá Học Cho Website DonorChoose.org

Đặt vấn đề

DonorsChoose.org được thành lập vào năm 2000 bởi một giáo viên lịch sử tại Mỹ tên là Bronx và đã huy động được 685 triệu đô la cho các lớp học. 3/4 các giáo viên ở các trường công lập ở Hoa Kỳ đã sử dụng Donor để gửi các yêu cầu bài tập cho học sinh. Từ đó, Donor trở thành nền tảng giáo dục hàng đầu hỗ trợ cho các vấn đề giáo dục công cộng.

Đến nay, hơn 3 triệu người dùng và đối tác đã đóng góp hơn 1,1 triệu dự án cho Donor. Nhưng các giáo viên vẫn phải tốn hàng tỷ đô tiền túi để chuẩn bị các dụng cụ học tập trên lớp (để truyền tải kiến thức cho học sinh).

Giải pháp được đưa ra ở đây là xây dựng một chiến dịch gợi ý cho các nhà tại trợ.

Phân tích dữ liệu

Chúng ta có các file sau:

File Donations.csv. Với mỗi dự án (Project ID), sẽ có 1 hoặc nhiều nhà quyên góp (Donor ID) mỗi cặp (dự án - nhà quyên góp sẽ định dang bằng 1 mã chung (Donation ID) và có các cột thông tin liên quan đến việc quyên góp đó). File có xấp xỉ 4.67 triệu dòng (chính xác là 4687844 dòng) và 7 cột. (Project ID - Định danh dự án, Donation ID - Định danh khoảng đóng góp (tưởng tượng như khoá tự tăng của bảng này đó các bạn), Donor ID - Mã định danh người đóng góp, Donation Included Option - hỗ trợ website donoschoose 15% giá trị quyên góp, Donation Amount - Số tiền quyên góp, Donor Cart Sequence - Thứ tự của dự án trọng bảng danh sách quyên góp,Donation Received Date - Ngày giờ quyên góp).

File Donors.csv. File định danh người quyên góp. Chứa tổng cộng hơn 2 triệu dòng( chính xác là 2122640 dòng) File có kích thước 2122640 x 5 với các thông tin cột là Donor ID (khoá chính, không trùng), Donor City (tên thành phố nhà đầu tư đang sinh sống), Donor State (tiểu bang mà người quyên góp đang sống), Donor is teacher, Donor Zip (3 ký tự đầu của mã bưu điện nhà từ thiện).

File Teacher.csv. File có tổng cộng 402900 dòng với các cột TeachId, Teacher Prefix (Mr, Mrs, Ms), Teacher First Project Posted Date.

File Schools.csv. File có tổng cộng 72994 dòng với các cột là SchoolID, SchoolName (tên trường có thể trùng nhau), School Metro Type ( phân loại trường thuộc 1 trong 5 nhóm : suburnban - ngoại ô, rural - nông thôn, uban - thành thị, town - thị trấn, unknow), School Percentage Free Lunch ( Số nguyên, mô tả tỷ lệ phần trăm số học sinh đủ điều kiện ăn trưa miễn phí hoặc ăn trưa giảm phí. Dữ liệu thu được cung cấp bởi một đối tác thống kê độc lập là NCES. Nếu trường nào không có giá trị do NCES cung cấp, chúng ta sẽ lấy số phần trăm này là trung bình phần trăm của các trường cùng huyện), School State (Trường đang toạ lạc ở bang nào (vd cali, Florida, Virginia, …)), School Zip (mã bưu chính), School City, School County

File Resources.csv. Với mỗi dự án, chúng ta cần các loại tài nguyên khác nhau. Các cột là Project ID (mã dự án), Resource Item Name (tên tài nguyên cần cho dự án đó vd project 000009891526c0ade7180f8423792063 cần ‘chair move and store cart’), Resource Quantity (số lượng tài nguyên cần, vd cần 1 cái ghế, 2 cái bảng v.v), Resource Unit Price (đơn giá cho 1 đơn vị tài nguyên, vd cái ghế giá 7 ngàn, cái bảng giá 10 ngàn, nếu 1 unit là ghế + bảng thì là 17 ngàn), Resource Vendor Name(nhà cung cấp, vd: Amazon Business, Woodwind and Brasswind).

File Projects.csv

Xây dựng chiến lược tiếp cận bài toán

Hãy xem đây như là bài toán gợi ý. Và Donors chính là hệ thống cung cấp các sản phẩm. Ví dụ đơn giản là bạn có website nghe nhạc mp3.zing.vn, alice vào nghe một hoặc một vài bài nhạc. Chúng ta sẽ xây dựng một hệ gợi ý những bài nhạc tiếp theo alice nên nghe dựa vào những bài nhạc đã nghe trước đó của alice. Tương tự vậy, hệ thống Donor như là website mp3.zing, bài nhạc tương tự như các project đang có, người dùng tương tự như các nhà tự thiện. Một khi một nhà từ thiện đã quyên góp cho 1 hoặc 1 nhón các dự án, chúng ta sẽ lên kế hoạch và gợi ý cho khác hàng dự án tiếp theo khách hàng nên tìm hiểu kỹ để xét xem có nên donate hay không.

Dựa vào các chiến lược trên, chúng ta có 3 cách có thể tiếp cận vấn đề:

  • Content-based filltering.
  • Collaborative Filtering
  • Hybrid methods

1. Tiền xử lý dữ liệu

Trước khi bắt đầu xây dựng chương trình gợi ý, chúng ta cần phải load dữ liệu lên bộ nhớ chính và làm sạch dữ liệu.

Trước tiên, chúng ta sẽ import các thư viện cần thiết. Nếu thiếu các thư viện nào, các bạn cứ pip install tên thư viện trong cmd/terminal là được

 1
 2import numpy as np
 3import scipy
 4import pandas as pd
 5import math
 6import random
 7import sklearn
 8from nltk.corpus import stopwords
 9from sklearn.model_selection import train_test_split
10from sklearn.feature_extraction.text import TfidfVectorizer
11from sklearn.metrics.pairwise import cosine_similarity
12from scipy.sparse.linalg import svds
13import matplotlib.pyplot as plt
14import os

Tiếp theo, chúng ta sẽ load 3 file Projects.csv, Donations.csv, Donors.csv lên và merge donations với donors.

 1# Set up test mode to save some time
 2test_mode = True
 3
 4# Read datasets
 5projects = pd.read_csv('../input/Projects.csv')
 6donations = pd.read_csv('../input/Donations.csv')
 7donors = pd.read_csv('../input/Donors.csv')
 8
 9#this piece of code converts Project_ID which is a 32-bit Hex int digits 10-1010
10# create column "project_id" with sequential integers
11f=len(projects)
12projects['project_id'] = np.nan
13g = list(range(10,f+10))
14g = pd.Series(g)
15projects['project_id'] = g.values
16
17# Merge datasets
18donations = donations.merge(donors, on="Donor ID", how="left")
19df = donations.merge(projects,on="Project ID", how="left")
20
21# only load a few lines in test mode
22if test_mode:
23    df = df.head(10000)
24
25donations_df = df

Ở giai đoạn xây dựng code và debug, mình chỉ load 10000 dữ liệu lên để test thử (để đảm bảo rằng mình code đúng - bằng cách set test_mode = True). Khi chạy thật mình sẽ set lại test_mode = False.

Thực hiện một vài bước phân tích kỹ thuật đơn giản để nắm rõ hơn về dữ liệu.

Thử đo mối quan hệ giữa các dự án và các “mạnh thường quân”

 1# Deal with missing values
 2donations["Donation Amount"] = donations["Donation Amount"].fillna(0)
 3
 4# Define event strength as the donated amount to a certain project
 5donations_df['eventStrength'] = donations_df['Donation Amount']
 6
 7def smooth_donor_preference(x):
 8    return math.log(1+x, 2)
 9
10donations_full_df = donations_df \
11                    .groupby(['Donor ID', 'Project ID'])['eventStrength'].sum() \
12                    .apply(smooth_donor_preference).reset_index()
13
14# Update projects dataset
15project_cols = projects.columns
16projects = df[project_cols].drop_duplicates()
17
18print('# of projects: %d' % len(projects))
19print('# of unique user/project donations: %d' % len(donations_full_df))
1# of projects: 1889
2# of unique user/project donations: 8648

Dựa vào kết quả trên tập test, chúng ta có thể đưa ra một vài nhận xét như sau:

  • Hầu hết các mạnh thường quân chỉ donate cho 1 project (tỷ lệ 86,48%)
  • Sẽ có trường hợp 1 mạnh thường quân sẽ donate cho nhiều dự án, và cũng có trường hợp 1 mạnh thường quân donate nhiều lần cho 1 dự án. Trường hợp này chiếm phần ít.

Để đánh giá mô hình, chúng ta sẽ chia tập dữ liệu thành 2 phần là train và test. Ở đây, chúng ta sẽ set tỷ lệ train/test là 20%.

2. Xây dựng mô hình Content-Based Filtering

Cách tiếp cận đầu tiên, chúng ta sẽ tìm những project gần giống với những project mà donor đã donated. Đơn giản nhất là với mỗi project, chúng ta sẽ định nghĩa các vector đặc trưng của chúng và đo độ giống nhau giữa hai vector đó. Vector đặc trưng chúng ta có thể xây dựng trên các thuộc tính như project type, project catefory, grade level, resource category, cost, school zip code, … hoặc các bạn có thể từ các vector cơ bản do tập dữ liệu cung cấp bổ sung thêm các vector cấp cao hơn, ví dụ như là rút trích các feature từ tên project hoặc mô tả của project, loại bỏ stopwords …

Ở đây, chúng ta sẽ sử dụng kỹ thuật TF-IDF để rút trích thông tin đặc trưng của dự án dựa trên project tittle và description. Về TF-IDF, các bạn có thể đọc ở một bài viết nào đó của google, mình không tiện nhắc đến nó chi tiết ở bài viết này.

a. Xây dựng tập đặc trưng
 1
 2# Preprocessing of text data
 3textfeats = ["Project Title","Project Essay"]
 4for cols in textfeats:
 5    projects[cols] = projects[cols].astype(str)
 6    projects[cols] = projects[cols].astype(str).fillna('') # FILL NA
 7    projects[cols] = projects[cols].str.lower() # Lowercase all text, so that capitalized words dont get treated differently
 8
 9text = projects["Project Title"] + ' ' + projects["Project Essay"]
10vectorizer = TfidfVectorizer(strip_accents='unicode',
11                             analyzer='word',
12                             lowercase=True, # Convert all uppercase to lowercase
13                             stop_words='english', # Remove commonly found english words ('it', 'a', 'the') which do not typically contain much signal
14                             max_df = 0.9, # Only consider words that appear in fewer than max_df percent of all documents
15                             # max_features=5000 # Maximum features to be extracted
16                            )
17project_ids = projects['Project ID'].tolist()
18tfidf_matrix = vectorizer.fit_transform(text)
19tfidf_feature_names = vectorizer.get_feature_names()
20
21
22## build profile
23
24def get_project_profile(project_id):
25    idx = project_ids.index(project_id)
26    project_profile = tfidf_matrix[idx:idx+1]
27    return project_profile
28
29def get_project_profiles(ids):
30    project_profiles_list = [get_project_profile(x) for x in np.ravel([ids])]
31    project_profiles = scipy.sparse.vstack(project_profiles_list)
32    return project_profiles
33
34def build_donors_profile(donor_id, donations_indexed_df):
35    donations_donor_df = donations_indexed_df.loc[donor_id]
36    donor_project_profiles = get_project_profiles(donations_donor_df['Project ID'])
37    donor_project_strengths = np.array(donations_donor_df['eventStrength']).reshape(-1,1)
38    #Weighted average of project profiles by the donations strength
39    donor_project_strengths_weighted_avg = np.sum(donor_project_profiles.multiply(donor_project_strengths), axis=0) / (np.sum(donor_project_strengths)+1)
40    donor_profile_norm = sklearn.preprocessing.normalize(donor_project_strengths_weighted_avg)
41    return donor_profile_norm
42
43from tqdm import tqdm
44
45def build_donors_profiles():
46    donations_indexed_df = donations_full_df[donations_full_df['Project ID'].isin(projects['Project ID'])].set_index('Donor ID')
47    donor_profiles = {}
48    for donor_id in tqdm(donations_indexed_df.index.unique()):
49        donor_profiles[donor_id] = build_donors_profile(donor_id, donations_indexed_df)
50    return donor_profiles
51
52donor_profiles = build_donors_profiles()
53print("# of donors with profiles: %d" % len(donor_profiles))
54
55mydonor1 = "6d5b22d39e68c656071a842732c63a0c"
56mydonor2 = "0016b23800f7ea46424b3254f016007a"
57mydonor1_profile = pd.DataFrame(sorted(zip(tfidf_feature_names,
58                        donor_profiles[mydonor1].flatten().tolist()),
59                        key=lambda x: -x[1])[:10],
60                        columns=['token', 'relevance'])
61mydonor2_profile = pd.DataFrame(sorted(zip(tfidf_feature_names,
62                        donor_profiles[mydonor2].flatten().tolist()),
63                        key=lambda x: -x[1])[:10],
64                        columns=['token', 'relevance'])
65
66print('feature of user ' + str(mydonor1))
67print(mydonor1_profile)
68
69print('feature of user ' + str(mydonor2))
70print(mydonor2_profile)

Mã nguồn ở trên cũng có chú thích đầy đủ, và đọc cũng dễ hiểu, nên mình không nói thêm gì nhiều. Mình tóm gọn một chút là chúng ta sẽ convert toàn bộ project tittle và description về dạng chữ thường, tách từ dựa vào khoảng trắng, loại bỏ những english stopwords. Sau đó xây dựng profile cho từng donor.

Kết quả

 1feature of  user 6d5b22d39e68c656071a842732c63a0c
 2        token  relevance
 30       music   0.450057
 41  auditorium   0.355256
 52        cart   0.272809
 63       chair   0.223861
 74   equipment   0.211338
 85   musicians   0.179244
 96        time   0.172908
107      moving   0.137749
118        ohms   0.134065
129     prepare   0.131274
13feature of  user 0016b23800f7ea46424b3254f016007a
14         token  relevance
150  pollinators   0.670222
161       plants   0.305398
172       module   0.223407
183  pollination   0.211870
194        seeds   0.180609
205      writing   0.166816
216        books   0.137455
227      reading   0.115003
238       weaved   0.111704
249         bees   0.101842

Nhìn kết quả trên, ta thấy rằng donor 1 có vẻ thích những thứ liên quan đến âm nhạc (music, auditorim), trong khi đó donor 2 thích những thứ liên quan đến trồng trọt (pollinators - thụ phấn, plants - cây cối)

b. Xây dựng mô hình

Việc xây dựng mô hình đến đây là khá đơn giản. Chúng ta chỉ việc tính khoảng cách cosin giữa vector cần dự đoán và toàn bộ vector có trong tập train rồi show top K prject có liên quan cao nhất

 1
 2
 3class ContentBasedRecommender:
 4
 5    MODEL_NAME = 'Content-Based'
 6
 7    def __init__(self, projects_df=None):
 8        self.project_ids = project_ids
 9        self.projects_df = projects_df
10
11    def get_model_name(self):
12        return self.MODEL_NAME
13
14    def _get_similar_projects_to_donor_profile(self, donor_id, topn=1000):
15        #Computes the cosine similarity between the donor profile and all project profiles
16        cosine_similarities = cosine_similarity(donor_profiles[donor_id], tfidf_matrix)
17        #Gets the top similar projects
18        similar_indices = cosine_similarities.argsort().flatten()[-topn:]
19        #Sort the similar projects by similarity
20        similar_projects = sorted([(project_ids[i], cosine_similarities[0,i]) for i in similar_indices], key=lambda x: -x[1])
21        return similar_projects
22
23    def recommend_projects(self, donor_id, projects_to_ignore=[], topn=10, verbose=False):
24        similar_projects = self._get_similar_projects_to_donor_profile(donor_id)
25        #Ignores projects the donor has already donated
26        similar_projects_filtered = list(filter(lambda x: x[0] not in projects_to_ignore, similar_projects))
27
28        recommendations_df = pd.DataFrame(similar_projects_filtered, columns=['Project ID', 'recStrength']).head(topn)
29
30        recommendations_df = recommendations_df.merge(self.projects_df, how = 'left',
31                                                    left_on = 'Project ID',
32                                                    right_on = 'Project ID')[['recStrength', 'Project ID', 'Project Title', 'Project Essay']]
33
34
35        return recommendations_df
36
37
38cbr_model = ContentBasedRecommender(projects)
39
40
41print('recommend for user ' + str(mydonor1))
42print(cbr_model.recommend_projects(mydonor1))
43
44print('recommend for user ' + str(mydonor2))
45print(cbr_model.recommend_projects(mydonor2))

Kết quả

 1recommend for user 6d5b22d39e68c656071a842732c63a0c
 2   recStrength                        ...                                                              Project Essay
 30     1.000000                        ...                          the music students in our classes perform freq...
 41     0.390997                        ...                          i have spent 12 years as an educator rebuildin...
 52     0.338676                        ...                          "music is what feelings sound like." -g. cates...
 63     0.331034                        ...                          true music is created not by the teacher but b...
 74     0.324355                        ...                          every morning my first grade students come to ...
 85     0.322923                        ...                          in today's fast paced environment, students ne...
 96     0.315910                        ...                          "music is a moral law.  it gives soul to the u...
107     0.314845                        ...                          i walk in the door so excited to get the stude...
118     0.310103                        ...                          some students have never put their hands on a ...
129     0.297516                        ...                          my students do not have money, but they do hav...
13
14[10 rows x 4 columns]
15recommend for user 0016b23800f7ea46424b3254f016007a
16   recStrength                        ...                                                              Project Essay
170     1.000000                        ...                          my students are creative, curious, and excited...
181     0.211962                        ...                          our school is a title 1 school.  100% of stude...
192     0.189111                        ...                          my students are active and eager learners who ...
203     0.188095                        ...                          being a small rural school we do a lot of trad...
214     0.173520                        ...                          "science is a way of life...science is the pro...
225     0.159015                        ...                          my second grade students love to come to schoo...
236     0.158071                        ...                          i teach 28 fourth graders in a neighborhood sc...
247     0.150389                        ...                          in my classroom we are working hard to become ...
258     0.144724                        ...                          as a teacher in a diverse, low-income, high-po...
269     0.139937                        ...                          have you ever been told you need to read, but ...
27
28[10 rows x 4 columns]

Mình dùng cmd nên bị giới hạn kết quả, các bạn có thể write log vào file hoặc dùng jupiter để show kết quả rõ hơn.

Ở đây, chúng ta nhận thấy rằng các recommend cho donor 1 thường là những project liên quan tới âm nhạc (nhìn tập feature ta cũng có thể đoán được). Và recommend cho donor 2 là những thứ liên quan đến chủ đề làm vườn và reading.

3. Collaborative Filtering Model

Lý thuyết về Collaborative Filtering Model các bạn có thể xem ở các bài viết khác của mình hoặc tham khảo thêm trên mạng. Ở đây, mình sẽ sử dụng Singular Value Decomposition (SVD) để xây dựng ma trận đặc trưng.

a. Xây dựng ma trận donor - project

Đầu tiên, chúng ta sẽ xây dựng ma trận mối quan hệ giữa donor và project. Nếu donor i có donated cho 1 project j thì dòng i cột j của ma trận sẽ được đánh dấu là 1, ngược lại là 0.

 1#### create matrix
 2#Creating a sparse pivot table with donors in rows and projects in columns
 3donors_projects_pivot_matrix_df = donations_full_df.pivot(index='Donor ID',
 4                                                          columns='Project ID',
 5                                                          values='eventStrength').fillna(0)
 6
 7# Transform the donor-project dataframe into a matrix
 8donors_projects_pivot_matrix = donors_projects_pivot_matrix_df.as_matrix()
 9
10# Get donor ids
11donors_ids = list(donors_projects_pivot_matrix_df.index)
12
13print(donors_projects_pivot_matrix[:5]) # print first 5 row
1
2array([[ 0.,  0.,  0., ...,  0.,  0.,  0.],
3       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
4       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
5       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
6       [ 0.,  0.,  0., ...,  0.,  0.,  0.]])
b. Singular Value Decomposition

Sau khi có ma trận trên, ta có một nhận xét rằng nó rất thưa, số lượng 0 thì nhiều mà 1 thì ít. Sau khi áp dụng SVD, ma trận kết quả sẽ ít thưa hơn (có thể đạt được đến mức không còn thưa nữa).

 1# Performs matrix factorization of the original donor-project matrix
 2# Here we set k = 20, which is the number of factors we are going to get
 3# In the definition of SVD, an original matrix A is approxmated as a product A ≈ UΣV
 4# where U and V have orthonormal columns, and Σ is non-negative diagonal.
 5U, sigma, Vt = svds(donors_projects_pivot_matrix, k = 20)
 6sigma = np.diag(sigma)
 7
 8# Reconstruct the matrix by multiplying its factors
 9all_donor_predicted_ratings = np.dot(np.dot(U, sigma), Vt)
10
11#Converting the reconstructed matrix back to a Pandas dataframe
12cf_preds_df = pd.DataFrame(all_donor_predicted_ratings,
13                           columns = donors_projects_pivot_matrix_df.columns,
14                           index=donors_ids).transpose()
15
16print(cf_preds_df.head())
 1                                  0003aba06ccf49f8c44fc2dd3b582411                ...                 ffff088c35d3455779a30898d1327b76
 2Project ID                                                                        ...
 3
 4000009891526c0ade7180f8423792063                     -3.423182e-34                ...-4.577244e-34
 500000ce845c00cbf0686c992fc369df4                     -3.061322e-36                ...-6.492305e-36
 600002d44003ed46b066607c5455a999a                      1.368936e-33                ...-2.239156e-32
 700002eb25d60a09c318efbd0797bffb5                      1.784576e-33                ...1.163684e-32
 80000300773fe015f870914b42528541b                      4.314216e-34                ...-4.666110e-34
 9
10[5 rows x 8015 columns]
c. Xây dựng Collaborative Filtering Model
 1
 2
 3class CFRecommender:
 4
 5    MODEL_NAME = 'Collaborative Filtering'
 6
 7    def __init__(self, cf_predictions_df, projects_df=None):
 8        self.cf_predictions_df = cf_predictions_df
 9        self.projects_df = projects_df
10
11    def get_model_name(self):
12        return self.MODEL_NAME
13
14    def recommend_projects(self, donor_id, projects_to_ignore=[], topn=10):
15        # Get and sort the donor's predictions
16        sorted_donor_predictions = self.cf_predictions_df[donor_id].sort_values(ascending=False) \
17                                    .reset_index().rename(columns={donor_id: 'recStrength'})
18
19        # Recommend the highest predicted projects that the donor hasn't donated to
20        recommendations_df = sorted_donor_predictions[~sorted_donor_predictions['Project ID'].isin(projects_to_ignore)] \
21                               .sort_values('recStrength', ascending = False) \
22                               .head(topn)
23
24
25        recommendations_df = recommendations_df.merge(self.projects_df, how = 'left',
26                                                          left_on = 'Project ID',
27                                                          right_on = 'Project ID')[['recStrength', 'Project ID', 'Project Title', 'Project Essay']]
28
29
30        return recommendations_df
31
32cfr_model = CFRecommender(cf_preds_df, projects)
33print(cfr_model.recommend_projects(mydonor1))
34
35print(cfr_model.recommend_projects(mydonor2))
 1[5 rows x 8015 columns]
 2    recStrength                        ...                                                              Project Essay
 30  3.015461e-17                        ...                          Our students are some of the hardest working k...
 41  2.237275e-17                        ...                          As Service Learning Coordinators at our elemen...
 52  2.188501e-17                        ...                          We are trying to engage more students in scien...
 63  1.768711e-17                        ...                          We are a brand new charter school that has onl...
 74  1.344489e-17                        ...                          Sitting at a desk for a sustained period of ti...
 85  9.957278e-18                        ...                          Our students come from a Title I school in Jer...
 96  6.932330e-18                        ...                          In my school 50% of the students are socioecon...
107  8.589640e-19                        ...                          Have you ever been told you need to read, but ...
118  6.698040e-19                        ...                          "I cannot say good-bye to those whom I have gr...
129  5.733941e-19                        ...                          I have students in class who are squinting and...
13
14[10 rows x 4 columns]
15    recStrength                        ...                                                              Project Essay
160  3.015461e-17                        ...                          Our students are some of the hardest working k...
171  2.237275e-17                        ...                          As Service Learning Coordinators at our elemen...
182  2.188501e-17                        ...                          We are trying to engage more students in scien...
193  1.768711e-17                        ...                          We are a brand new charter school that has onl...
204  1.344489e-17                        ...                          Sitting at a desk for a sustained period of ti...
215  9.957278e-18                        ...                          Our students come from a Title I school in Jer...
226  6.932330e-18                        ...                          In my school 50% of the students are socioecon...
237  8.589640e-19                        ...                          Have you ever been told you need to read, but ...
248  6.698040e-19                        ...                          "I cannot say good-bye to those whom I have gr...
259  5.733941e-19                        ...                          I have students in class who are squinting and...

Kết quả trả về có vẻ không được đẹp như ở phương pháp trên. Ở đây, thuật toán dựa vào hành vi donated của những người khác có điểm tương đồng với user donor 1 và 2. Bởi vậy gợi ý những project sẽ khác những gợi ý ở phương pháp 1.

4. Hybrid Method

Phương pháp lai này kết hợp cả 2 hướng tiếp cận của hai phương pháp ở trên. Ở đây, chúng ta sẽ xây dựng một mô hình nhỏ, nhân điểm của content based và collaborative filtering lại với nhau, sau đó xếp hạng để được điểm hybrid. Đây là 1 cách đơn giản, các bạn có thể tìm đọc nhiều cách tiếp cận khác và ứng dụng vào bài toán.

 1class HybridRecommender:
 2
 3    MODEL_NAME = 'Hybrid'
 4
 5    def __init__(self, cb_rec_model, cf_rec_model, projects_df):
 6        self.cb_rec_model = cb_rec_model
 7        self.cf_rec_model = cf_rec_model
 8        self.projects_df = projects_df
 9
10    def get_model_name(self):
11        return self.MODEL_NAME
12
13    def recommend_projects(self, donor_id, projects_to_ignore=[], topn=10):
14        #Getting the top-1000 Content-based filtering recommendations
15        cb_recs_df = self.cb_rec_model.recommend_projects(donor_id, projects_to_ignore=projects_to_ignore,
16                                                           topn=1000).rename(columns={'recStrength': 'recStrengthCB'})
17
18        #Getting the top-1000 Collaborative filtering recommendations
19        cf_recs_df = self.cf_rec_model.recommend_projects(donor_id, projects_to_ignore=projects_to_ignore,
20                                                           topn=1000).rename(columns={'recStrength': 'recStrengthCF'})
21
22        #Combining the results by Project ID
23        recs_df = cb_recs_df.merge(cf_recs_df,
24                                   how = 'inner',
25                                   left_on = 'Project ID',
26                                   right_on = 'Project ID')
27
28        #Computing a hybrid recommendation score based on CF and CB scores
29        recs_df['recStrengthHybrid'] = recs_df['recStrengthCB'] * recs_df['recStrengthCF']
30
31        #Sorting recommendations by hybrid score
32        recommendations_df = recs_df.sort_values('recStrengthHybrid', ascending=False).head(topn)
33
34        recommendations_df = recommendations_df.merge(self.projects_df, how = 'left',
35                                                    left_on = 'Project ID',
36                                                    right_on = 'Project ID')[['recStrengthHybrid',
37                                                                              'Project ID', 'Project Title',
38                                                                              'Project Essay']]
39
40
41        return recommendations_df
42
43hybrid_model = HybridRecommender(cbr_model, cfr_model, projects)
44
45
46print(hybrid_model.recommend_projects(mydonor1))
47
48print(hybrid_model.recommend_projects(mydonor2))
 1   recStrengthHybrid                        ...                                                              Project Essay
 20       1.574375e-18                        ...                          we are trying to engage more students in scien...
 31       1.221807e-18                        ...                          in my school 50% of the students are socioecon...
 42       1.214293e-18                        ...                          our students are some of the hardest working k...
 53       4.037232e-19                        ...                          sitting at a desk for a sustained period of ti...
 64       6.661794e-20                        ...                          music expresses that which cannot be put into...
 75       4.872264e-20                        ...                          i walk in the door so excited to get the stude...
 86       4.410098e-20                        ...                          i have spent 12 years as an educator rebuildin...
 97       2.907349e-20                        ...                          "music is what feelings sound like." -g. cates...
108       2.121616e-20                        ...                          "i cannot say good-bye to those whom i have gr...
119       1.353927e-20                        ...                          our band program is one of the largest in our ...
12
13[10 rows x 4 columns]
14   recStrengthHybrid                        ...                                                              Project Essay
150       2.811124e-18                        ...                          in this modern, digital age, i would like to u...
161       1.249967e-18                        ...                          we are a brand new charter school that has onl...
172       6.055628e-19                        ...                          my students are african american and hispanic....
183       5.912367e-19                        ...                          the a. community and its students are a very s...
194       2.541749e-19                        ...                          do you want to go on an adventure and learn ab...
205       2.494812e-19                        ...                          the average day in my class involves students ...
216       2.323313e-19                        ...                          i teach ela (reading component) to self-contai...
227       1.271629e-19                        ...                          hi there! do you want to help to instill a lif...
238       1.044990e-19                        ...                          having writing utensils is essential for stude...
249       1.004780e-19                        ...                          there's no such thing as a kid who hates readi...
25
26[10 rows x 4 columns]

Kết quả trả ra tốt hơn nhiều so với cách 2, donor1 có music, donor2 có cây trồng và sách.

5. Đánh giá mô hình

Có rất nhiều cách khác nhau để đánh giá mô hình recommend system. Một trong các cách mình sử dụng ở đây là sử dụng độ đo top K accuracy. Độ đo này được tính như sau:

Với mỗi user: Với mỗi item user đã pick trong test set Lấy mẫu 1000 item khác mà người dùng chưa bao giờ pick

Cảm ơn các bạn đã theo dõi. Hẹn gặp bạn ở các bài viết tiếp theo. Cố lên.

Comments