ลดมิติข้อมูลเพื่อทำ Recommender System By Unsupervised Learning

Mr.P L
mmp-li
Published in
4 min readApr 23, 2019

ทำไม netflix หรือ youtube ถึงได้แนะนำวิดีโอแนวนี้มาให้เรา ?

ห่างหายไปนานกับบทความ Machine Learning เพราะว่าช่วงนี้แอบหนีไปเล่น apex ไม่ยอมเขียนบทความสักที 5555 และช่วงก่อนสงกรานต์ได้เรียนคอร์สที่ชื่อว่า

Unsupervised Learning in Python

ที่จริงก่อนหน้านี้ก็ได้เรียนในคอร์สของ edX มาแล้วแต่ก็ยังไม่เห็นประโยชน์ รู้แค่ว่ามันจัดกลุ่มได้ ลดมิติได้ จนกระทั่งได้มาเรียนคอร์สนี้ถึงจะได้เอามาใช้จริงๆ เลยถึงจะเข้าใจส่วนวันนี้จะใช้งานแค่การลดมิติ (dimension reduction) ส่วนการจัดกลุ่มแบบ Unsupervised ขอเป็นบทความหน้าแล้วกัน

https://medium.com/mlrecipies/machine-learning-basics-supervised-learning-theory-part-1-d910b96d56fc

ทฤษฏีการทำระบบ Recommender System

ปกติเวลาเราเข้าไปดู youtube เราจะเจอวิดีโอที่แนะนำโดยถ้าสังเกตุดีๆมันจะอ้างอิงกับสิ่งที่เราดูในช่วงนี้หรือค้นหาในช่วงนี้

apex เต็มไปหมดเลย

หรือแม้แต่ netflix จะอ้างอิงกับเรื่องที่เราพึ่งดูจบไป ระบบจะหาหนังที่คล้ายๆกันมาให้เราดูต่อได้เลย

โดยหลักๆระบบ Recommend จะมีอยู่ 2 แบบที่เราเห็นกันเยอะๆคือ

  1. อ้างอิงจากสิ่งที่เราชอบ/กำลังติดตาม/ค้นหา (Content based)
  2. อ้างอิงจากคนอื่นเช่น เราอ่านบทความนี้เสร็จก็จะเจอหัวข้อประมาณว่า “คนอื่นได้อ่านข่าวนี้ต่อ” (Collaborative filtering)

โดยระบบ Recommend จะใช้สิ่งที่ตรงกับผู้ใช้ได้อ่าน/ดูไปมาทำการ match กับสิ่งที่คล้ายๆกันเช่น เราสมมุติหนังเรื่อง Friend Zone กับ I Fine Thank You Love You

หนังสองเรื่องนี้มีส่วนที่คล้ายกันอยู่มาก โดยเรายกตัวอย่างมาให้เห็นหลักๆเลยคือความรัก หนังไทย และตลก แล้วมาเปรียบเทียบกับอีกเรื่องคือ Game of throne กับ Friend Zone

หน้าหนาวกำลังมา

เราไม่ต้องทำรูปเปรียบเทียบเลยด้วยซ้ำ เพราะสองเรื่องนี้ไม่คล้ายกันเลยเพราะฉนั้นมันจะไม่ขึ้นใน recommend ของเราแน่นอน

ทำไมต้องลดมิติข้อมูล

หาใครได้ลองทำงานด้าน NLP โดยใช้ CountVectorizer หรือ tf-idf ถ้าเรามีข้อมูลขนาดใหญ่หรือจำนวนที่เยอะจะพบว่าข้อมูลของเรามันจะมีขนาดใหญ่มาก ! และยิ่งเรามีจำนวนข้อมูลเยอะ มันจะยิ่งใหญ่ขึ้นไปอีก ถ้าเราสามารถลดมิติของข้อมูลจากเดิมอาจจะมีถึง 30,000 แต่สามารถลดเหลือ 50 ได้ละ ? โดยที่ข้อมูลไม่เสียหายและได้ผลลัพธ์ที่ดีกว่าเดิมละ ? แค่คิดมันก็สุดยอดแล้ว

โดยบทความนี้ไม่ได้จะใช้ได้แค่กับการทำ recommender system การลดมิติข้อมูลสามรถใช้เลือก feature ที่ “มีผล” จริงๆกับ model อีกด้วย เราไม่ต้องเลือก feature เองก็ได้ ให้ Unsupervised (PCA,NMF) เลือกให้เราแทนได้เลย

แล้วบทความนี้จะทำ…. ?

วันนี้เราจะทำ Recommender System ที่เกี่ยวกับ “หัวข้อข่าว” ซึ่งเราจะจำลองว่าเราทำเว็บอ่านข่าวและต้องการจะแสดงข่าวที่ผู้อ่านชื่นชอบและเกี่ยวข้องกับที่ผู้อ่านสนใจนั้นเอง โดยเราจะใช้ dataset ที่เป็นภาษาอังกฤษทั้งหมด (ภาษาไทยก็ใช้ได้เหมือนๆกัน)

เริ่ม !

ทำการโหลด dataset มาจาก kaggle ก่อน

โดยรอบนี้เราจะทำแค่ 50,000 หัวข้อ จากทั้งหมด 143,000 ถ้าใครอยากจะทำทั้งหมดก็เอาไฟล์มาต่อกันได้เลย

ทีนี้เราเปิดไฟล์ของเราก่อนแล้วลองสังเกตุ dataset ดูก่อน

import pandas as pd
file = pd.read_csv(‘articles1.csv’)
file.head()

โดยเราจะใช้แค่ title ของ dataset นี้โดยเราจะเอาหัวข้อข่าวมาเชื่อมโยงหากันนั้นเอง

จากนั้นให้เราสร้าง bag-of-word ขึ้นมาสองอัน อันแรกสร้างด้วย CountVectorizer ส่วนอีกอันสร้างด้วย tf-idf เพื่อเปรียบเทียบผลลัพธ์ของทั้ง 2 อันนี้

ตรงนี้ถ้าใครงงว่า bag-of-word / CV / tf-idf คืออะไรให้ย้อนกลับไปอ่านบทความ NLP ที่เคยเขียนอธิบายเอาไว้แล้วก่อน จะได้เข้าใจมากยิ่งขึ้น

from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
vectorizer.fit(file[‘title’])
X2= vectorizer.transform(file[‘title’])
X2[0].shape

และสร้าง bag-of-word อีกอันด้วย TfidfVectorizer

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
vectorizer.fit(file[‘title’])
X1 = vectorizer.transform(file[‘title’])
X1[0].shape

โดยเมื่อทำการสร้าง bag-of-word ทั้ง 2 อันขึ้นมาแล้วจะได้ X ที่มีขนาด(1 , 27423) ! หมายความว่าเรามีข้อมูลทั้งหมด 50,000 * 27,423 = 1,371,150,000 Record !!! ถ้าเราเห็นข้อมูลเยอะขนาดนี้แล้วเรายังฝืนใช้งานมันต่อมีหวังคอมพังแน่นอน เราจะต้องลดขนาดข้อมูลของมันก่อนที่จะนำไปใช้งานนั้นเอง !

เรียก library ที่จะใช้งานขึ้นมา

from sklearn.decomposition import NMF
from sklearn.preprocessing import Normalizer, MaxAbsScaler
from sklearn.pipeline import make_pipeline

ทีนี้มาถึงจุดที่ใช้งานง่าย แต่เข้าใจยาก

อันแรก MaxAbsScaler() เป็นการปรับสเกลค่าในข้อมูลของเราโดยที่แปลงค่าในแถวนั้นๆให้ค่าสูงสุดมีค่าเป็นบวกไม่ติดลบนั้นเอง (ไม่ทำให้การกระจายตัวของข้อมูลเสียหายด้วย)

scaler = MaxAbsScaler()

อันที่สอง NMF() ชื่อเต็มๆคือ Non-negative matrix factorization แปลไทยง่ายๆคือ ทำหน้าที่ลดมิติของข้อมูลโดยที่ข้อมูลจะต้องไม่มีค่าเป็นลบ

nmf = NMF(n_components=50)

อันสุดท้าย Normalizer() เป็นการปรับค่าใน matrix ของเราให้อยู่ในรูปแบบ unit form โดยใช้วิธี l1 หรือ l2 โดยวิธีเป็นวิธีพื้นฐานสำหรับการทำ text classification หรือ การทำ clustering นั้นเอง

normalizer = Normalizer()

จากนั้นสร้าง pipline ขึ้นมาโดยเป็นการรวมเอา 3 อย่างเข้าด้วยกันโดยเรียงลำดับดังนี้

ทำการ scale ข้อมูล -> ลดมิติข้อมูล -> ปรับค่าในข้อมูลเพื่อทำ dot matrix

pipeline = make_pipeline(scaler,nmf,normalizer)

จากนั้นให้เอา pipline ของเรามาเทรนกับข้อมูลที่เราแปลงมาจาก CV กับ tf-idf

norm_features1 = pipeline.fit_transform(X1)
norm_features2 = pipeline.fit_transform(X2)

จากนั้นให้เราทำการสร้าง dataframe ขึ้นมาจาก pandas เพื่อเอาไว้ใช้ในอนาคตโดยไม่ต้องเทรนอีกต่อไป

df1 = pd.DataFrame(norm_features1,index=file[‘title’])
df2 = pd.DataFrame(norm_features2,index=file[‘title’])

โดยเมื่อเราลองส่องดูข้างใน df1,df2 ของเราจะพบว่าขนาดของมันถูกลดเหลือ (1,50) จาก (1,27423) ด้วยวิธี NMF นั้นเองซึ่งทำให้ข้อมูลของเราถูกลดเหลือเพียง 50,000 * 50 = 2,500,000 Record !!! หายไปเกือบๆ 1.368 billion Record !!

ทีนี้ได้เวลาลองทำงานจริงว่าการลดมิติจะส่งผลกระทบอะไรบ้างหรือไม่ และ bag-of-word แต่ละให้ผลลัพธ์ต่างกันขนาดไหน

ให้สร้างฟังก์ชั่นขึ้นมาก่อนอธิบายทีละคำสั่งประมาณนี้

  • df.loc[] จะทำการดึงข้อมูล 50 column จาก index ของเรา โดยเราจะต้องใส่หัวข้อข่าวให้ครบทุกตัวอักษร เพื่อที่จะดึงข้อมูลได้ถูก index นั้นเอง
  • df.dot() ตรงนี้เป็นการ dot ของ matrix คือ การหาความคล้ายของหัวข้อข่าวด้วยการทำ cosine กับทุกๆข่าวที่เรามี
  • similarities.nlargest() หลังจากที่ทำ dot เสร็จแล้วผลลัพธ์จะอยู่ใน similarities และคำสั่ง nlargest() ก็จะแสดง ชื่อหัวข่าว ที่มี ค่าความคล้าย มากที่สุดนั้นเอง
def similarities(article,df):
article_index = df.loc[article]
# Compute cosine similarities: similarities
similarities = df.dot(article_index)
# Display those with highest cosine similarity
print(similarities.nlargest())

ทีนี้ลองค้นหาหัวข้อข่าวที่ความคล้ายกับ ‘The Atlantic Daily: Passing the Presidential Mic’ แปลไทยก็คือ ข่าวจากสำนัก Atlantic : การส่งต่อตำแหน่งประธานาธิบดี

ทีนี้ลองกับ tf-idf ก่อน

similarities(‘The Atlantic Daily: Passing the Presidential Mic’,df1)

ผลลัพธ์ : หัวข้อข่าวไม่ค่อยจะตรงเท่าไร แต่ที่ถูกต้องแบบเห็นชัดเลยคือชื่อสำนักข่าว แต่หัวข้อจริงๆไม่ถูกเลย ไม่มีใครสื่อได้ถึงประธานาธิบดีเลย แถมมี Grammys Award มาอีก มาได้ไงงงงงง

สาเหตุ : tf-idf นับความถี่ของคำและตัดคำยิบๆย่อยๆออกไป ทำให้คำหลักๆ ซึ่งก็คือชื่อสำนักข่าว ถูกให้ความสำคัญมากกว่าปกติทำให้เราเห็นหัวข้อข่าวที่มีชื่อสำนักข่าวขึ้นมาก่อนนั้นเอง

ทีนี้ลองกับ CountVectorizer

similarities('The Atlantic Daily: Passing the Presidential Mic',df2)

ผลลัพธ์ : คำว่า ‘Presidential’ ถูกยกให้เป็นคำหลักในรอบนี้ ทำให้ข่าวทุกๆข่าวจะเกี่ยวข้องกับประธาธิบดี แถมอันที่สองยังเป็นเรื่องของการดีเบตตำแหน่งประธานาธิบดีอีก ถือว่าแม่นยำพอตัวเลยสำหรับ CountVectorizer

บทสรุป

การลดมิติข้อมูลหากเราสามารถเข้าใจมันได้แบบแท้จริงมันจะเป็นประโยชน์อย่างมากไม่ว่าจะเป็นการเลือก Feature ที่แท้จริง หรือการลดขนาดข้อมูลไม่ให้มากเกินความจำเป็น และเรายังสามารถนำข้อมูลเหล่านี้ไปใช้ได้โดยที่ไม่มีความเสียหายเกิดขึ้นกับข้อมูลเราอีก

Github : https://github.com/peeratpop/Recommend-System-NEWS

Medium : https://medium.com/@pingloaf

Linkedin : https://www.linkedin.com/in/peerat-limkonchotiwat/

บทความนี้เป็นส่วนหนึ่งของบทความ

เริ่มเรียน Machine/Deep Learning 0–100 (Introduction)

--

--

Mr.P L
mmp-li
Editor for

Lifestyle of Programmer & IoT (Node-RED|Blynk) & Data Science (ML,DL,NLP) and Whatever I want to do | cat can coding too | Ph.D. -> VISTEC -> IST