<style>
text-align: justify;
</style>
# Recommender System: Book Genome
<div style="text-align: center;" markdown="1">

</div>
- Trường THPT chuyên Trần Đại Nghĩa - 12CTin
- Tác giả:
- Nguyễn Phạm Quốc An
- Phan Lê Tường Bách
- Nguyễn Xuân Nghĩa
- Bùi Quốc Minh Nhật
- Nguyễn Khánh Trang
Mục lục
=================
* 1 [Giới thiệu](#1-Giới-thiệu)
* 1.1 [Recommender System là gì?](#11-Recommeder-System)
* 1.2 [Ứng dụng của Recommender System](#12-Ứng-dụng-của-Recommender-System)
* 1.3 [Giới thiệu về Tag Genome](#13-Giới-thiệu-về-Tag-Genome)
* 2 [Dữ liệu](#2-Dữ-liệu)
* 2.1 [Thu thập dữ liệu](#21-Thu-thập-dữ-liệu)
* 2.2 [Xử lý dữ liệu](#22-Xử-lý-dữ-liệu)
* 3 [Xây dựng model](#3-Xây-dựng-model)
* 3.1 [Lưu đồ xây dựng model](#31-Lưu-đồ-xây-dựng-model)
* 3.2 [Xây dựng model: cross validation](#32-Xây-dựng-model-cross-validation)
* 3.3 [Huấn luyện model](#33-Huấn-luyện-model)
* 3.4 [Xây dựng cấu trúc dữ liệu Book Genome](#34-Xây-dựng-cấu-trúc-dữ-liệu-Book-Genome)
* 4 [Kết luận](#4-Kết-luận)
* 4.1 [Kết quả đạt được](#41-Kết-quả-đạt-được)
* 4.2 [Hướng phát triển và mở rộng](#42-Hướng-phát-triển-và-mở-rộng)
## 1. Giới thiệu
### 1.1 Recommender System là gì?
Recommender System (hệ thống gợi ý) là một loại phần mềm hoặc hệ thống thông tin tự động có khả năng dự đoán và đề xuất các mục, sản phẩm hoặc nội dung mà người dùng có thể quan tâm dựa trên lịch sử tương tác hoặc sở thích trước đó. Mục tiêu chính của hệ thống gợi ý là cung cấp trải nghiệm cá nhân hóa và giúp người dùng khám phá những nội dung mới hiệu quả hơn.
<div style="text-align: center;" markdown="1">

***Lưu đồ thể hiện nguyên lý hoạt động cơ bản của một hệ thống gợi ý***
</div>
### 1.2 Ứng dụng của Recommender System
Các hệ thống gợi ý đã đặt nền tảng cho mọi công ty công nghệ lớn xung quanh chúng ta cung cấp dịch vụ bán lẻ, video-on-demand hoặc âm nhạc trực tuyến và qua đó đã định hình lại cách chúng ta mua sắm, tìm một bài nhạc mới, tìm địa điểm đi du lịch hay thậm chí là tìm kiếm một người bạn. Từ việc tìm kiếm sản phẩm tốt nhất trên thị trường đến việc tìm kiếm một người bạn cũ trực tuyến hoặc nghe nhạc khi lái xe, các hệ thống gợi ý đều có mặt khắp mọi nơi.
Đối với những công ty cung cấp dịch vụ, hệ thống gợi ý giúp lọc lượng thông tin khổng lồ từ tất cả người dùng và cơ sở dữ liệu để phù hợp với sở thích cá nhân của người dùng. Ví dụ, Amazon sử dụng nó để đề xuất sản phẩm cho khách hàng, và Spotify sử dụng nó để quyết định bài hát nào sẽ phát tiếp theo cho người dùng.
### 1.3 Giới thiệu về Tag Genome
"Tag genome" thường được sử dụng để mô tả một cấu trúc dữ liệu trong đó gồm hệ thống của các "tag" (nhãn) được gắn kết (mã hóa) với dữ liệu bằng các thuật toán, đặc biệt là các thuật toán học máy. Cụ thể, tag genome có thể áp dụng cho việc đánh giá, phân loại hoặc tổ chức thông tin.
Một "tag" có thể là một từ khóa, nhãn, hoặc thẻ được gán cho một phần dữ liệu để mô tả hoặc định rõ tính chất của nó tùy vào ngữ cảnh mà "tag" được nhắc đến. "Tag genome" có thể được sử dụng để mô tả cách mà các tag được sắp xếp, tổ chức và liên kết với nhau, giống như cách một "genome" (gen) mô tả cấu trúc nhiễm sắc thể của một sinh vật.
Ví dụ, trong hệ thống đánh giá sản phẩm, mỗi sản phẩm có thể được gắn kết với các "tag" mô tả như "chất lượng cao," "giá cả phải chăng," "mẫu mã đẹp," "phù hợp với gia đình," v.v
Ngoài ra, "Tag genome" còn mô tả cách các "tag" này được kết hợp và sắp xếp trong không gian đa chiều, giúp hiểu rõ hơn về mối quan hệ và ưu tiên giữa chúng và với dữ liệu.
Book genome là tên gọi của tag genome dành riêng sách, gắn kết các nhãn về "năm xuất bản", "tên sách", "id sách", "id người đọc sách", "tên tác giả", và các tag về "thể loại" với nhau. Dự án này sẽ sử dụng Book genome để xây dựng một hệ thống gợi ý cho trang đọc sách Goodreads (https://goodreads.com/) dựa vào dữ liệu thu thập sẽ được mô tả dưới đây.
## 2 Dữ liệu
### 2.1 Thu thập dữ liệu
Hệ thống gợi ý sách là một ứng dụng quan trọng trong việc cung cấp trải nghiệm đọc sách cá nhân hóa cho người dùng. Để xây dựng một hệ thống như vậy, chúng tôi đã thực hiện quá trình thu thập nhiều tập dữ liệu khác nhau để đảm bảo chất lượng của dữ liệu:
* 'ratings.csv': Dataset được thu thập từ Goodreads, chứa thông tin về đánh giá sách từ người dùng. Cấu trúc dataset bao gồm các cột 'user_id', 'book_id' và 'ratings'.
<div style="text-align: center;" markdown="1">

</div>
* 'books.csv': Dataset được thu thập từ Goodreads, cung cấp tất cả thông tin về sách như 'book_id', 'title', 'author', v.v. Thông tin này quan trọng để hiểu về nội dung của từng cuốn sách.
<div style="text-align: center;" markdown="1">

</div>
* 'tags.csv': Dataset được thu thập từ Goodreads và Kaggle, chứa danh sách các từ khóa (tags) và 'tag_id' đặc trưng cho từng từ khóa.
<div style="text-align: center;" markdown="1">

</div>
* 'book_tags.csv': Dataset được thu thập từ Goodreads và Kaggle, liên kết các tags với từng cuốn sách, cung cấp thông tin chi tiết về các tags nào được sử dụng nhiều cho mỗi cuốn sách.
<div style="text-align: center;" markdown="1">

</div>
* 'to_read.csv': Dataset được thu thập từ Goodreads và Kaggle, chứa danh sách sách mà người dùng đánh dấu để đọc sau này. Điều này có thể giúp hệ thống dự đoán sở thích đọc sách tương lai của người dùng.
<div style="text-align: center;" markdown="1">

</div>
### 2.2 Xử lý dữ liệu
Sau khi thu thập được 5 dataset thô, việc ta cần làm là tiền xử lý dữ liệu và gộp thành những dataset có thể sử dụng để huấn luyện model. Mô hình xử lý dữ liệu được biểu diễn một cách tổng quát qua lưu đồ dưới đây:
<div style="text-align: center;" markdown="1">

***Lưu đồ xử lý dữ liệu***
</div>
Lưu đồ phía trên cơ bản gồm **4** bước:
1. **Thu thập các tập dữ liệu**.
2. **Data Exploration** (khai phá dữ liệu): Bước này được thực hiện nhằm hiểu rõ về các xu hướng đọc sách và đánh giá sách trong từng tập dữ liệu. Tại bước này ta nhận xét được rằng:
* Tập **ratings** là tập dữ liệu không cần xử lý thêm.
<div style="text-align: center;" markdown="1">

</div>
* Tương tự với tập **ratings**, tập dữ liệu **to_read** cũng không cần xử lý.
<div style="text-align: center;" markdown="1">

</div>
* Tập dữ liệu **tags** không cần xử lý thêm
<div style="text-align: center;" markdown="1">

</div>
* Tập dữ liệu **books** có những ô bị trống trong các trường 'isbn', 'isbn13', 'original_publication_year', 'language_code' nên các trường này sẽ không được sử dụng và bị loại bỏ khỏi tập dữ liệu sau khi xử lý.
<div style="text-align: center;" markdown="1">

</div>
* Tập dữ liệu **book_tags** có một số hàng dữ liệu bị trùng nên cần loại bỏ và xử lý để đảm bảo đặc trưng của dữ liệu.
<div style="text-align: center;" markdown="1">

</div>
3. **Data Processing** (xử lý dữ liệu): Nhận xét về mối quan hệ giữa các tập dữ liệu và kết nối các tập dữ liệu thô thành một tập dữ liệu có thể sử dụng để huấn luyện model. Tại bước này, ta nhận xét được rằng:
* Giữa tập dữ liệu 'ratings' và 'books' có trường 'book_id' chung nên ta hoàn toàn có thể kết nối các tập dừng liệu thông qua trường dữ liệu này.
* Giữa 3 tập dữ liệu 'tags', 'book_tags' và 'books' có các trường dữ liệu 'goodreads_book_id' và 'tag_id' chung nên có thể kết nối chúng thông qua trường dữ liệu này.
<div style="text-align: center;" markdown="1">

</div>
Ngoài ra, ta còn cần loại bỏ các hàng dữ liệu bị trùng của tập **book_tags**
<div style="text-align: center;" markdown="1">

</div>
4. **Data cleaning** (làm sạch dữ liệu): là thuật ngữ dùng cho việc xử lý dữ liệu nhằm tổng hợp và ghi nhận các thông số của bộ dữ liệu. Đối với bộ dữ liệu của book genome, bước nào gồm 2 thao tác:
* Phân nhóm các tags:
* Ta nhận thấy rằng có 2 loại tag: **thể loại** và **các loại tag do người dùng đặt (như favourite, owned...)**
* Các tag dạng **thể loại** là phân biệt với nhau. Tuy giữa 2 thể loại có thể có mối quan hệ gần gũi với nhau như supernatural và fiction. Thế nhưng ta có thể phân biệt chúng dựa vào cấu trúc diễn ngôn (discourse structure). Điều này cũng đã được chứng minh trong bài báo nghiên cứu "Investigating Genre Distinctions through Discourse Distance and Discourse Network" của 3 nhà khoa học Trung Quốc Kun Sun, Rong Wang và Wenxin Xiong.
<div style="text-align: center;" markdown="1">

</div>
Tuy nhiên, các dạng tag **do người dùng đặt** lại có những tên gọi khác nhau dù mang cùng một giá trị. Ví dụ như tag 'favorite' và tag 'favs'.
<div style="text-align: center;" markdown="1">

***Các tags có cùng giá trị***
</div>
Do đó ta cần nhóm các tags này lại thành một nhóm duy nhất là Favorite để đảm bảo tính ổn định của bộ dữ liệu dùng để huấn luyện mô hình.
## 3 Xây dựng model
### 3.1 Lưu đồ xây dựng model
<div style="text-align: center;" markdown="1">

</div>
### 3.2 Xây dựng model: cross validation
Để so sánh sự chính xác giữa các model, ta sẽ sử dụng phương pháp Cross-validation.
Các model ta sẽ so sánh là:
- NormalPredictor (của thư viện Surprise)
- KNN
- SVD
- NMF
Với KNN, SVD và NMF, ta sẽ kết hợp thêm với Grid Search để tìm ra các hyperparameter tối ưu cho từng model.
Với mỗi model, ta sẽ chạy 3-fold Cross-validation và lưu kết quả đánh giá để có thể so sánh các model ở bước cuối.
Đầu tiên, ta phải đọc dữ liệu được lọc và lưu nó vào biến:
```python!
# Đọc file dataset
r = pd.read_csv('ratings_filtered_data.csv')
r = r.drop('Unnamed: 0', axis=1)
# Khai báo Reader của thư viện Surprise (cần thiết để lưu dataset)
reader = Reader(rating_scale=(1, 5))
# Chọn 100000 datapoints đầu
r_select = r[:100000]
# Lưu dataset vào biến sử dụng hàm của thư viện Surprise
data_set = Dataset.load_from_df(r_select, reader)
```
Sau đây là quá trình chạy Cross-validation với từng model:
- NormalPredictor:
```python
# khai báo model
nd = NormalPredictor()
# Chạy 3-fold cross-validation
nd_cv = cross_validate(nd, data_set, measures=['RMSE', 'MAE'], cv=3, verbose=True)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append('Normal Predictor')
Model_Summary ['GridSearch (Y/N)'].append('N')
Model_Summary ['Paramters'].append('-')
Model_Summary ['RMSE'].append(nd_cv['test_rmse'].mean())
```
- KNN không có Grid Search:
```python
# khai báo model
knn = KNNWithMeans(k=50, sim_options={'name': 'pearson_baseline', 'user_based': True})
# Chạy 3-fold cross-validation
knn_cv = cross_validate(knn, data_set, measures=['RMSE', 'MAE'], cv=3, verbose=True)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'] = ['KNNWithMeans']
Model_Summary ['GridSearch (Y/N)'] = ['N']
Model_Summary ['Paramters'] = ['k=50, name: pearson_baseline, user_based: True,min_support = 1}']
Model_Summary ['RMSE'] = [knn_cv['test_rmse'].mean()]
```
- KNN có Grid Search:
```python
# khai báo các giá trị có thể cho các hyperparameters
k = [30,40,50]
sim_options = {
"name": ["msd", "cosine"],
"min_support": [3, 4, 5],
"user_based": [False, True],
}
param_grid = {'k':k, "sim_options": sim_options}
# khai báo model
gs_knn = GridSearchCV(KNNWithMeans, param_grid, measures=["rmse", "mae"], cv=3)
# train model
gs_knn.fit(data_set)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append('KNNWithMeans')
Model_Summary ['GridSearch (Y/N)'].append('Y')
Model_Summary ['Paramters'].append(gs_knn.best_params["rmse"])
Model_Summary ['RMSE'].append(gs_knn.best_score["rmse"])
```
- SVD không có Grid Search:
```python
# khai báo model
svd = SVD(n_factors=20, n_epochs = 30, biased=False)
# Chạy 3-fold cross-validation
svd_cv = cross_validate(svd, data_set, measures=['RMSE', 'MAE'], cv=3)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append('SVD')
Model_Summary ['GridSearch (Y/N)'].append('N')
Model_Summary ['Paramters'].append('n_factors=20, n_epochs = 30, biased=False')
Model_Summary ['RMSE'].append(svd_cv['test_rmse'].mean())
```
- SVD có Grid Search:
```python
# khai báo các giá trị có thể cho các hyperparameters
param_grid = {'n_factors': [10,20,30,50], 'n_epochs': [50,100,200], 'lr_all': [0.005],'reg_all': [0.05], 'biased': [True]}
# khai báo model
gs_svd = GridSearchCV(SVD, param_grid, measures=['rmse'], cv = 3)
# train model
gs_svd.fit(data_set)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append('SVD')
Model_Summary ['GridSearch (Y/N)'].append('Y')
Model_Summary ['Paramters'].append(gs_svd.best_params)
Model_Summary ['RMSE'].append(gs_svd.best_score['rmse'])
```
- NMF không có Grid Search:
```python
# khai báo model
nmf = NMF(n_factors=20, n_epochs = 30, biased = True) # initiate a SVD algorithm object
# Chạy 3-fold cross-validation
nmf_cv = cross_validate(nmf, data_set, measures=['RMSE', 'MAE'], cv=3)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append(' NMF')
Model_Summary ['GridSearch (Y/N)'].append ('N')
Model_Summary ['Paramters'].append('n_factors=20, n_epochs = 30, biased = True')
Model_Summary ['RMSE'].append(nmf_cv['test_rmse'].mean())
```
- NMF có Grid Search:
```python
# khai báo các giá trị có thể cho các hyperparameters
param_grid = {'n_factors': [10,20,30,50], 'n_epochs': [20, 30,40,50],'biased': [False, True]}
# khai báo model
gs_nmf = GridSearchCV(NMF, param_grid, measures=['rmse'], cv=3)
# train model
gs_nmf.fit(data_set)
# Lưu kết quả chạy (tên model, có Grid Search hay không, giá trị các hyperparamters, điểm đánh giá)
Model_Summary ['Model_Name'].append('NMF')
Model_Summary ['GridSearch (Y/N)'].append('Y')
Model_Summary ['Paramters'].append(gs_nmf.best_params["rmse"])
Model_Summary ['RMSE'].append(gs_nmf.best_score["rmse"])
```
Ta có kết quả chạy các model như sau:
<div style="text-align: center;" markdown="1">

</div>
Do mô hình SVD có điểm đánh giá tốt nhất, nên mô hình tốt nhất ở đây là SVD.
### 3.3 Huấn luyện model
Ta sẽ áp dụng model hoạt động tốt nhất trên dữ liệu ẩn và so sánh kết quả
```python
# Chọn một tập ngẫu nhiên để thử với mô hình tốt nhất
hold_data = r[100000:200000]
# Tạo một thang điểm đánh giá
reader = Reader(rating_scale=(1, 5))
# Lựa chọn dữ liệu ngẫu nhiên từ tập đã chọn
data_set = Dataset.load_from_df(hold_data,reader)
#Phân chia train và test data
train , test = train_test_split(data_set, test_size = 0.5)
# Tạo mô hình SVD với các tham số tốt nhất
svd = SVD(n_factors = 30, n_epochs = 50, lr_all = 0.005, reg_all = 0.05, biased = True)
svd.fit(train)
test_pred = svd.test(test)
accuracy.rmse(test_pred)
```
Làm tròn các dự đoán và so sánh biểu đồ:
```python
comparison = []
actual_rating = []
for user, item, rating in test:
actual_rating.append(rating)
#plt.hist(actual_rating, color = 'g')
for item in test_pred:
comparison.append((item[3]))
#plt.hist(comparison,color = 'b')
dataset = pd.DataFrame()
dataset ['actual_rating'] =actual_rating
dataset ['predicted_rating'] = (comparison)
dataset [['actual_rating','predicted_rating']].plot(kind='hist',bins=[0, 1, 2, 3, 4, 5], alpha=0.5)
plt.xlabel('Rating')
plt.title('Comparison of Histograms: Actual Rating vs Predicted Rating')
plt.show()
```
<div style="text-align: center;" markdown="1">

***Biểu đồ so sánh***
</div>
### 3.4 Xây dựng cấu trúc dữ liệu Book Genome
Bây giờ ta sẽ xây dựng một cấu trúc dữ liệu chứa kết quả dự đoán của model bao gồm các thông số: user_id, book_id, predicted_ratings.
```python
prediction = {'user_id': [], 'book_id': [],'Predicted Rating': [] }
for element in test_pred:
prediction['user_id'].append(element.uid)
prediction['book_id'].append(element.iid)
prediction['Predicted Rating'].append(element.est)
#prediction
prediction_dataframe = pd.DataFrame.from_dict(prediction)
prediction_dataframe
```
<div style="text-align: center;" markdown="1">

</div>
## 4. Kết luận
### 4.1 Kết quả đạt được
Dựa trên các nội dung đã trình bày, chúng ta có thể hiểu thêm về hệ thống gợi ý (Recommendation System) và ứng dụng của Tag Genome trong dự án này. Thông qua việc thu thập và phân tích các tập dữ liệu từ Goodreads và Kaggle, chúng tôi đã xây dựng được các mô hình dự đoán (Normal Predictor, KNN, SVD, NMF) và kết luận rằng mô hình SVD mang lại hiệu quả tốt nhất. Qua đó huấn luyện và phát triển cấu trúc dữ liệu cho dự án dựa trên mô hình SVD này.
Kết quả thu được là một hệ thống gợi ý với 2 chức năng:
1. Gợi ý cho người dùng đã từng sử dụng dịch vụ đọc sách Goodreads.
<div style="text-align: center;" markdown="1">

</div>
2. Gợi ý cho người dùng mới, chưa từng sử dụng dịch vụ đọc sách Goodreads.
<div style="text-align: center;" markdown="1">

</div>
### 4.2 Hướng phát triển và mở rộng
Để nâng cao hiệu suất của mô hình, ta có thể nghiên cứu các biện pháp mở rộng thêm đầu vào, tối ưu hóa các siêu tham số huấn luyện của mô hình để nâng cao chất lượng của việc gợi ý sản phẩm. Ngoài ra, bằng các dataset thu được qua việc chạy model, ta có thể phát triển thêm các tính năng khác như gợi ý sách qua hình ảnh bìa sách.