01Overall Concept
ไอเดียหลัก — Weighted KNN จำตัวอย่างเก่าไว้ เมื่อมีคำถามใหม่จะหา K ตัวอย่างที่ใกล้ที่สุด แล้วให้ตัวอย่างที่ใกล้กว่ามีสิทธิ์ออกเสียงมากกว่า ใช้ได้ทั้งการเลือกคลาสและการทำนายตัวเลข ต่างจาก KNN แบบปกติที่เพื่อนบ้านทุกคนมีเสียงเท่ากัน
ลองนึกภาพว่า — ถ้าจะประเมินราคาบ้าน เราอาจถามบ้านข้างกันและบ้านในละแวกเดียวกัน แต่ให้ความสำคัญกับบ้านที่ใกล้เคียงทั้งขนาดและทำเลมากกว่า ไม่ใช่นับทุกคำตอบเท่ากัน
ตัวอย่างการนำไปใช้ — จำแนกชนิดดอกไม้จากขนาดกลีบ ประมาณราคาบ้านจากคุณสมบัติที่คล้ายกัน หรือใช้เป็น baseline สำหรับข้อมูลตารางที่มี feature ไม่มาก
02อธิบายภาษาคนเข้าใจง่ายๆ
ลองนึกว่าเรามีดอกไม้หนึ่งดอกที่ยังไม่รู้สายพันธุ์ เราวัดขนาดกลีบแล้วไปหาดอกไม้เก่าที่มีขนาดใกล้เคียงกัน วิธี KNN ธรรมดาถามเพื่อนบ้าน K ดอกแล้วให้แต่ละดอกโหวตหนึ่งเสียง
Weighted KNN ถามเพิ่มว่า แต่ละดอกใกล้เราแค่ไหน ถ้าแทบเหมือนกันเลย ก็ควรมีเสียงมากกว่าดอกที่ต่างออกไปมาก แม้จะติดรายชื่อ K ดอกเหมือนกันก็ตาม
สมมติพบสายพันธุ์ A ที่ระยะ 2 และ 4 ส่วนสายพันธุ์ B อยู่ที่ระยะ 0.5 เมื่อใช้น้ำหนัก 1 หารระยะ A ได้ 0.5 + 0.25 = 0.75 แต่ B ได้ 2 จึงเลือก B แม้ A จะมีจำนวนมากกว่า
ถ้าโจทย์เป็นการทายราคา เราไม่โหวตคลาส แต่เอาราคาแต่ละบ้านคูณน้ำหนัก รวมกัน แล้วหารด้วยผลรวมน้ำหนัก บ้านที่คล้ายกันมากจึงดึงคำตอบเข้าใกล้ราคาของตนเองมากกว่า
คำว่าใกล้จะมีความหมายก็ต่อเมื่อหน่วยเหมาะสม ถ้าใส่ราคาหลักล้านคู่กับจำนวนห้องหลักหน่วยโดยไม่ปรับสเกล คอลัมน์ราคาจะกลบอย่างอื่นไป ควรปรับสเกลด้วยข้อมูลฝึกก่อน
ภาพจำ: ให้คนที่อยู่ใกล้คำถามที่สุดถือไมโครโฟนที่ดังกว่า แต่ยังต้องระวังคนที่อยู่ใกล้มากและให้ข้อมูลผิด
03How it works
ค่อยๆ ทำไปด้วยกัน ตั้งแต่ข้อมูลแถวแรกจนถึงการตรวจคำตอบ
เตรียมตัวอย่างที่วัดความคล้ายได้
แยก X เช่น ความยาวและความกว้างกลีบดอก ออกจาก y ซึ่งเป็นสายพันธุ์ แบ่ง train/test แบบ stratify เพื่อให้มีทุกคลาสทั้งสองฝั่ง และเก็บข้อมูลซ้ำของดอกเดียวกันไว้ฝั่งเดียว
ทำให้หน่วยเปรียบเทียบกันได้
ใช้ make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=7, weights="distance")) แล้ว fit เฉพาะชุดฝึก ตัว scaler จำค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานจากชุดฝึกไว้ใช้กับข้อมูลใหม่
วัดระยะและคัด K จุด
สำหรับคำถามใหม่ คำนวณ Euclidean distance กับข้อมูลฝึก แล้วเลือก K จุดที่ระยะน้อยที่สุด เช่นระยะ [2, 4, 0.5] ให้ค่าน้ำหนัก [0.5, 0.25, 2] ถ้ามีระยะศูนย์ ให้ใช้เฉพาะจุดที่ตรงกันเหล่านั้นแทนการหารด้วยศูนย์
รวมคำตอบตามน้ำหนัก
Classification รวมคะแนนน้ำหนักแยกแต่ละคลาสแล้วเลือกค่าสูงสุด ส่วน KNeighborsRegressor ใช้ค่าเฉลี่ยถ่วงน้ำหนักของ y ใน Playground พื้นที่สีแสดงคลาสที่โมเดลทาย และจุดขอบเข้มเป็นชุดทดสอบที่ไม่ได้ใช้โหวต
เลือก K จากข้อมูลที่ไม่ใช้ฝึก
ลองหลายค่า K ภายใน cross-validation เปรียบเทียบ weights="uniform" กับ "distance" แล้วตรวจ accuracy หรือ macro F1 บนชุดทดสอบครั้งสุดท้าย K เล็กตามรายละเอียดได้มาก แต่ไวต่อ noise และการโหวตแบบถ่วงน้ำหนักไม่ได้ดีกว่าเสมอ
ข้อมูล พารามิเตอร์ และภาพรวมการคำนวณ
n_neighborsweightspข้อมูลที่รับเข้ามา
X เป็นตาราง feature ที่ปรับสเกลแล้ว และ y เป็นคลาสหรือตัวเลข เก็บตัวอย่างฝึกไว้สำหรับค้นหาเพื่อนบ้าน
ระยะ [2, 4, 0.5] · คลาส [A, A, B]พารามิเตอร์ที่ใช้
- n_neighbors
- จำนวน K ของเพื่อนบ้านที่ใช้
- weights
- เลือก distance เพื่อใช้น้ำหนักแปรผกผันกับระยะ
- p
- p=2 คือระยะ Euclidean เมื่อ metric เป็น minkowski
ผลลัพธ์ที่ได้
ได้คลาสจากคะแนนรวม หรือค่าทำนายแบบค่าเฉลี่ยถ่วงน้ำหนัก
A = 0.75 · B = 2 → ทาย Bคำนวณทีละขั้น
- 01
หา K จุดที่มีระยะ d น้อยที่สุด
- 02
ให้น้ำหนัก w=1/d; ถ้า d=0 ใช้เฉพาะจุดระยะศูนย์
- 03
รวม w ต่อคลาส หรือใช้ Σ(wy)/Σw สำหรับ regression
ควรรู้อีกนิด — เมื่อมีเพื่อนบ้านระยะศูนย์ ให้ใช้เฉพาะคำตอบของจุดที่ตรงกัน อย่าแทน 1/0 ด้วยตัวเลขตามใจ และอย่าเลือก K จากคะแนนชุดทดสอบซ้ำ ๆ
04Make it yours
# ติดตั้ง: pip install numpy scikit-learn# ดาวน์โหลด sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์import numpy as np data = np.genfromtxt("weighted-knn-sample.csv", delimiter=",", skip_header=1)X = data[:, :2] from sklearn.neighbors import KNeighborsClassifierfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import StandardScalerfrom sklearn.metrics import accuracy_score, classification_report y = data[:, 2].astype(int)# กันทุกแถวที่ 5 ไว้ทดสอบ ตามข้อมูลสาธิตใน Playground# งานจริงควรเลือก split ให้เหมาะกับเวลา/บุคคล/กลุ่มของข้อมูลis_test = np.arange(len(X)) % 5 == 0X_train, X_test = X[~is_test], X[is_test]y_train, y_test = y[~is_test], y[is_test]model = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=7, weights="distance"))model.fit(X_train, y_train)pred = model.predict(X_test)print("Test accuracy:", accuracy_score(y_test, pred))print(classification_report(y_test, pred, labels=[0, 1, 2], zero_division=0))# Python fit scaler จากชุดฝึก; Playground ใช้พิกัดหาร 100# จึงอาจได้คำตอบต่างกันเล็กน้อย โค้ดเปลี่ยนตามค่าที่เลือกใน Playground · แต่ละภาษาใช้ข้อมูลตัวอย่างตามที่ระบุในโค้ด
05Learn by playing
ปรับ จำนวนเพื่อนบ้าน K แล้วสังเกตรูปแบบคำทำนายและตัวชี้วัดที่คำนวณใหม่ทันที
Playground เป็น implementation เพื่อการเรียนรู้ที่คำนวณในเบราว์เซอร์ ส่วนโค้ด Python ใช้ scikit-learn จึงอาจให้ค่าต่างกันเล็กน้อยจากรายละเอียดการ optimize และค่าเริ่มต้นของไลบรารี ผลนี้ไม่ใช่ benchmark บนข้อมูลจริง