การทำระบบเเนะนำหนังสือ โดยใช้ K-Nearest Neighbors
Tags: python, ai
Published: 2024-04-10 13:44:15

บทความนี้จะเป็นการทดลองการสร้างระบบเเนะนำหนังสือที่มีอยู่ในชุดข้อมูลหนังสือโดยจะใช้   K-Nearest Neighbors หรือ KNN เพื่อเเนะนำหนังสือที่คล้ายกัน
 
K-Nearest Neighbors ถือเป็น Supervised Machine Learning Algorithm ใช้ในการจัดหมวดหมู่หรือการถดถอย 

ซึ่งทำงานได้โดยการกำหนดค่า K ซึ่งก็คือจำนวน Training Samples ที่มีระยะทางใกล้กับ Data Point ใหม่มากที่สุดและทำนายจาก Majority ของ K samples เหล่านั้น

---

## ขั้นตอนการทำ

> <u>ขั้นที่ 1 : นำเข้า Libraries ที่ต้องใช้ทั้งหมดลงใน Google Colab</u>


```
import numpy as np
import pandas as pd
from scipy.sparse import csr_matrix
from sklearn.neighbors import NearestNeighbors
import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_colwidth', None)

import os, sys
import re
```

> <u>ขั้นที่ 2 : โหลด dataset : books</u>

โดยตัวข้อมูลจะอยู่ใน folder data ซึ่งสามารถเรียกใช้ โดยทำการเขียนชื่อ folder/ชื่อไฟล์ 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctt4z78f05ri69sc7tql.png)

ดาวน์โหลด dataset ตัวอย่าง --> [<u>HERE</u>](https://www.kaggle.com/code/danishammar/book-recommender-knn/input)

**Code ดาวน์โหลดข้อมูล** 

```
df_books = pd.read_csv('data/Books.csv')
df_books.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fdfnjmd62np4as65apt6.png)


> <u>ขั้นที่ 3 : ตรวจสอบชื่อคอลัมน์ </u>

แสดงรายชื่อคอลัมน์ทั้งหมดใน DataFrame `df_books`

```
df_books.columns
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xyvxhp3eb313k8opl5ji.png)


> <u>ขั้นที่ 4 : ลบคอลัมน์ที่ไม่ต้องการออก </u>

```
df_books = df_books[['ISBN', 'Book-Title', 'Book-Author']]
```

> <u>ขั้นที่ 5 : โหลด dataset : ratings</u>

ดาวน์โหลด dataset ตัวอย่าง --> [<u>HERE</u>](https://www.kaggle.com/code/danishammar/book-recommender-knn/input)
**Code ดาวน์โหลดข้อมูล** 
```
df_ratings = pd.read_csv('data/Ratings.csv')
df_ratings.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nydxqyyzjddgs6plcp0y.png)


> <u>ขั้นที่ 6 : ตรวจสอบค่า Null</u>

นับจำนวนของค่า null หรือ missing values ในแต่ละคอลัมน์ของ DataFrame และแสดงผลรวมของค่า null ในแต่ละคอลัมน์ซึ่งจะมีประโยชน์ในการตรวจสอบความสมบูรณ์ของข้อมูลหรือการจัดการกับค่าที่ขาดหายไปใน DataFrame.
```
df_books.isnull().sum()
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/noqk1akol3xcl4ij8j3j.png)

```
df_ratings.isnull().sum()
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zzjkmygqtq41iygwrnqb.png)

> <u>ขั้นที่ 7 : ดรอปค่า Null</u>

ลบแถวที่มีค่า null (หรือ missing values) ออกจาก DataFrame
```
df_books.dropna(inplace=True)
df_books.isnull().sum()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8sej1mkx6mvvlodqnam5.png)


> <u>ขั้นที่ 8 : ตรวจสอบขนาดของ Dataset</u>
```
df_books.shape
```
จะได้ output ออกมาเป็น tuple ที่มีสมาชิกสองตัวเป็น (จำนวนแถว, จำนวนคอลัมน์)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpw58a4mwzlxh51q1s1t.png)

```
df_ratings.shape
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1crujfgn0yg2u0liaxk1.png)


> <u>ขั้นที่ 9 : เรียงลำดับการให้คะเเนน</u>

```
# Calculate the count of ratings given by each user and store it in the 'ratings' Series
ratings = df_ratings['User-ID'].value_counts()
# Sort the 'ratings' Series in descending order based on the counts of user IDs
ratings.sort_values(ascending=False).head()
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ws6ke2xlbmmeo9ucnut.png)



> <u>ขั้นที่ 10 : ตรวจสอบผู้ใช้ที่มีคะเเนนน้อยกว่า 200</u>

```
len(ratings[ratings < 200])
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/by06h9y7d7dchl2h8fcw.png)


```
df_ratings['User-ID'].isin(ratings[ratings < 200].index).sum()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbozpqv1bzngcb7a2dfu.png)


```
df_ratings_rm = df_ratings[
  ~df_ratings['User-ID'].isin(ratings[ratings < 200].index)
]
df_ratings_rm.shape
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvsa2db9dsxca2dfw076.png)

> <u>ขั้นที่ 11 : ตรวจสอบหนังสือที่มีคะเเนนน้อยกว่า 100</u>


```
ratings = df_ratings['ISBN'].value_counts() 
ratings.sort_values(ascending=False).head()
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8u3f75lqz0hm86seaq1.png)

```
len(ratings[ratings < 100])
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5bo8gotkendash2b09nb.png)


```
df_books['ISBN'].isin(ratings[ratings < 100].index).sum()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugupy2n7qoat9nwzujhd.png)

```
df_ratings_rm = df_ratings_rm[
  ~df_ratings_rm['ISBN'].isin(ratings[ratings < 100].index)
]
df_ratings_rm.shape
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8brgw0thu06pl5r91wc8.png)


```
# These should exist
books = ["Where the Heart Is (Oprah's Book Club (Paperback))",
        "I'll Be Seeing You",
        "The Weight of Water",
        "The Surgeon",
        "I Know This Much Is True"]

for book in books:
    print(df_ratings_rm['ISBN'].isin(df_books[df_books['Book-Title'] == book]['ISBN']).sum())
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qk3jl182cvow94boflnj.png)

> <u>ขั้นที่ 12 : ประมวลผลชุดข้อมูลล่วงหน้าสำหรับ Machine LearningMachine Learning </u>

แสดงผลลัพธ์ของ DataFrame `df_ratings_rm` 
```
df_ratings_rm.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ta1kd86l3h45a318o34g.png)

แสดงผลลัพธ์ของ DataFrame `df_books` 
```
df_books.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8mocxswoh50pyqz4s60.png)

สร้างตารางเชิงกระจาย (pivot table) จาก DataFrame `df_ratings_rm` โดยให้ 'User-ID' เป็น index และ 'ISBN' เป็น columns และ 'Book-Rating' เป็นค่าที่ใช้ในการเปลี่ยนรูปแบบของตารางโดยใช้ code ดังนี้
```
df = df_ratings_rm.pivot_table(index=['User-ID'],columns=['ISBN'],values='Book-Rating').fillna(0).T
df.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ynjsp2i3b0wqaf13r9v.png)

`df.index`คือการที่นำ DataFrame `df` กับ DataFrame `df_books` มารวมกันซึ่งทำให้เราสามารถเข้าถึงข้อมูลหนังสือได้โดยอ้างอิงชื่อของหนังสือแทนที่จะอ้างอิงด้วย ISBN โดยใช้ code ดังนี้ 
```
df.index = df.join(df_books.set_index('ISBN'))['Book-Title']
```

ทำการเรียงลำดับแถวของ DataFrame `df` ตาม index(ชื่อของหนังสือ) ให้เรียงลำดับตามตัวอักษรโดยใช้ code ดังนี้ 
```
df = df.sort_index()
df.head()
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sg4re6z0as9vgnyzrxb6.png)

แสดงคะแนนการจัดอันดับของหนังสือโดยใช้หนังสือตัวอย่างคือหนังสือ "The Queen of the Damned (Vampire Chronicles (Paperback))" โดยใช้ code ดังนี้
```
df.loc["The Queen of the Damned (Vampire Chronicles (Paperback))"][:5]
```
ซึ่งจะเเสดง 5 คอลัมน์แรกใน DataFrame `df` ออกมาเป็นข้อมูลดังนี้:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ht52aoo1d5oqo03rzvie.png)

---

## สร้างเเบบจำลอง K-Nearest Neighbors

```
model = NearestNeighbors(metric='cosine')
model.fit(df.values)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iv742llx5o8pdy39ai4s.png)

```
df.iloc[0].shape
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrvknqtdtunz8up094xi.png)

```
title = 'The Queen of the Damned (Vampire Chronicles (Paperback))'
df.loc[title].shape
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrvknqtdtunz8up094xi.png)

```
distance, indice = model.kneighbors([df.loc[title].values], n_neighbors=6)

print(distance)
print(indice)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lxpbtplgdozo736xep9.png)

```
df.iloc[indice[0]].index.values
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0iliot8mevfyd3yi35v.png)

```
pd.DataFrame({
    'title'   : df.iloc[indice[0]].index.values,
    'distance': distance[0]
}) \
.sort_values(by='distance', ascending=False)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6z1lnycwq4d9ckxvsncw.png)

---

## สร้างฟังก์ชันค้นหาหนังสือแนะนำจากหนังสือที่ input ลงไป

```
# function to return recommended books - this will be tested
def get_recommends(title = ""):
  try:
    book = df.loc[title]
  except KeyError as e:
    print('The given book', e, 'does not exist')
    return

  distance, indice = model.kneighbors([book.values], n_neighbors=6)

  recommended_books = pd.DataFrame({
      'title'   : df.iloc[indice[0]].index.values,
      'distance': distance[0]
    }) \
    .sort_values(by='distance', ascending=False) \
    .head(5).values

  return [title, recommended_books]
```
---

## การใช้งานระบบเเนะนำหนังสือ

> 1.ลองทดลองระบบเเนะนำหนังสือโดยใช้หนังสือ The Queen of the Damned (Vampire Chronicles (Paperback))

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nadyi0c1n0xrcb1h5gv6.png)

```
books = get_recommends("The Queen of the Damned (Vampire Chronicles (Paperback))")
print(books)
```

ผลลัพธ์ที่ได้ :
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nw2vsrqcl0aip95p9uc.png)

> 2.ทดลองระบบเเนะนำหนังสือโดยใช้หนังสือที่ไม่มีใน dataset

```
books = get_recommends("Dreamsnake")
print(books)
```

ผลลัพธ์ที่ได้ :
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43w8s2fl7xtuynvw32dk.png)

##สรุปผล
จากการทำลองใช้งานระบบเเนะนำหนังสือจะเห็นได้ว่าถ้าเรา input หนังสือที่มีอยู่ใน data เเละให้ระบบเเนะนำหนังสือให้ระบบก็จะเเนะนำหนังสือที่มีความคล้ายคลึงกันที่อยู่ใน data ออกมาให้ 5 เล่ม เเต่ถ้าหนังสือที่เรา input เข้าไปไม่อยู่ใน data ระบบก็จะขึ้นเตือนว่า The given book '...' does not exist 

## ขอขอบคุณข้อมูลจาก

- 
https://kongruksiam.medium.com/%E0%B8%AA%E0%B8%A3%E0%B8%B8%E0%B8%9B-machine-learning-ep-4-%E0%B9%80%E0%B8%9E%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%99%E0%B8%9A%E0%B9%89%E0%B8%B2%E0%B8%99%E0%B9%83%E0%B8%81%E0%B8%A5%E0%B9%89%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%AA%E0%B8%B8%E0%B8%94-k-nearest-neighbors-787665f7c09d

- https://www.kaggle.com/code/danishammar/book-recommender-knn/notebook#Preprocess-Dataset-For-Machine-Learning