01Overall Concept
ไอเดียหลัก — Radius Neighbors หาเพื่อนบ้านด้วยระยะที่ยอมรับได้ แทนการบังคับจำนวน K ถ้าอยู่กลางกลุ่มหนาแน่นก็มีคนช่วยตอบมาก ถ้าอยู่ในบริเวณห่างไกลอาจไม่มีใครตอบเลย จึงเหมาะเมื่อความหมายของคำว่าใกล้สำคัญกว่าจำนวนตัวอย่าง
ลองนึกภาพว่า — ถ้าต้องการรู้ว่าแถวบ้านฝนตกไหม เราอาจถามทุกคนในรัศมีหนึ่งกิโลเมตร แทนที่จะถามให้ครบห้าคนจนต้องลากคนที่อยู่อีกจังหวัดมารวมด้วย
ตัวอย่างการนำไปใช้ — จำแนกจากตัวอย่างในละแวกที่คล้ายกัน ประมาณค่าจากเซนเซอร์ใกล้เคียง หรือใช้ระบบที่ยอมเว้นคำตอบเมื่อข้อมูลใหม่ไกลจากสิ่งที่เคยเห็น
02อธิบายภาษาคนเข้าใจง่ายๆ
KNN ถามว่าใครใกล้ที่สุด K คน ส่วน Radius Neighbors ถามว่าใครบ้างที่ใกล้ไม่เกินระยะที่เรายอมรับ จำนวนผู้ตอบจึงไม่ตายตัว
นึกภาพวงกลมรอบบ้านเรา ทุกบ้านที่อยู่ในวงมีสิทธิ์โหวตว่าละแวกนี้เป็นย่านแบบไหน วงเล็กเก็บเฉพาะบ้านใกล้จริง ๆ วงใหญ่เริ่มดึงย่านอื่นเข้ามาด้วย
ถ้าในวงมี A สามจุดและ B หนึ่งจุด การโหวตแบบเท่ากันเลือก A แต่ถ้าไม่มีจุดในวงเลย โมเดลไม่มีหลักฐานเฉพาะบริเวณนั้น เราต้องกำหนดว่าจะงดตอบหรือใช้กฎสำรอง
ในตัวอย่างนี้ สีเทาหมายถึงไม่มีเพื่อนบ้าน ไม่ใช่คลาสที่สี่ คะแนน accuracy นับจุดที่งดตอบว่าไม่ถูกด้วย จึงควรดูจำนวนที่ตอบได้ประกอบกัน
ระยะ 0.15 ใน Playground วัดหลังหารพิกัดด้วย 100 ให้เป็นช่วง 0–1 ถ้าเปลี่ยนหน่วยหรือปรับสเกลอีกแบบ ก็ต้องเลือกรัศมีใหม่ ตัวเลขเดิมไม่จำเป็นต้องใช้ได้กับทุกชุดข้อมูล
ภาพจำ: ขีดวงว่าคำแนะนำจากที่ไหนยังใกล้พอ และยอมรับเมื่อไม่มีใครอยู่ในวง
03How it works
ค่อยๆ ทำไปด้วยกัน ตั้งแต่ข้อมูลแถวแรกจนถึงการตรวจคำตอบ
แบ่งข้อมูลก่อนเลือกวง
เตรียม X และ y แล้วกันชุดทดสอบไว้ ใช้เฉพาะข้อมูลฝึกเป็นเพื่อนบ้าน ถ้าเป็นงานพื้นที่จริง ระยะทางควรมีความหมายกับพิกัดและไม่ให้ข้อมูลที่อยู่ติดกันมากรั่วข้ามชุดจนคะแนนสูงเกินจริง
กำหนดหน่วยของ radius
ในโค้ดใช้ MinMaxScaler ภายใน pipeline เพื่อแปลง feature จากชุดฝึก แล้วตั้ง RadiusNeighborsClassifier(radius=0.15, outlier_label=-1) โดยคลาสจริงเป็น 0, 1, 2 และ -1 สงวนไว้แทนการงดตอบ
ค้นหาทุกคนในวง
คำนวณระยะจากคำถามใหม่ถึงข้อมูลฝึก เก็บเฉพาะ d ≤ radius จำนวนเพื่อนบ้านแต่ละคำถามจึงต่างกันได้ ไม่ต้องเติมจุดไกลเพื่อให้ครบ K
รวมคำตอบและจัดการวงว่าง
โหวตคลาสด้วยจำนวนเพื่อนบ้านเมื่อ weights="uniform" ถ้าวงว่างให้ -1; ค่าเริ่มต้น outlier_label=None ของ classifier จะเกิดข้อผิดพลาดเมื่อทำนายจุดที่ไม่มีเพื่อนบ้าน ส่วน regression ต้องกำหนดการรับมือค่าที่ทายไม่ได้แยกต่างหาก
ตรวจทั้งความถูกต้องและการครอบคลุม
เปรียบเทียบ accuracy บนชุดทดสอบทั้งหมดกับ coverage ซึ่งเป็นสัดส่วนที่มีเพื่อนบ้าน เลือกรัศมีด้วย validation แล้วค่อยประเมิน test อีกครั้ง รัศมีที่แม่นเฉพาะจุดจำนวนน้อยอาจยังไม่เหมาะใช้งานจริง
ข้อมูล พารามิเตอร์ และภาพรวมการคำนวณ
radiusweightsoutlier_labelข้อมูลที่รับเข้ามา
X เป็น feature เชิงระยะทางที่มีสเกลเหมาะสม และ y เป็นคำตอบของแต่ละแถว
radius=0.15 · พบคลาส [A, A, A, B]พารามิเตอร์ที่ใช้
- radius
- ระยะมากที่สุดที่ยังถือเป็นเพื่อนบ้าน
- weights
- uniform ให้เสียงเท่ากัน หรือ distance ถ่วงตามระยะ
- outlier_label
- คำตอบสำรองสำหรับ classifier เมื่อวงว่าง
ผลลัพธ์ที่ได้
คลาสหรือค่าทำนายต่อแถว และสถานะที่ไม่มีเพื่อนบ้าน
A ได้ 3 จาก 4 เสียง → A · วงว่าง → -1คำนวณทีละขั้น
- 01
คำนวณ d จากคำถามใหม่ถึงตัวอย่างฝึก
- 02
เก็บทุกจุดที่ d ≤ radius
- 03
โหวตหรือเฉลี่ย; ถ้าวงว่างใช้กฎที่กำหนดไว้
ควรรู้อีกนิด — เมื่อไม่มีเพื่อนบ้าน ตัวอย่างนี้คืน -1 เพื่อแสดงการงดตอบ ไม่ใช่คลาสใหม่ ถ้าใช้ outlier_label="most_frequent" จะเป็นกฎสำรองให้คลาสที่พบมากที่สุดและต้องอธิบายให้ผู้ใช้ทราบ
04Make it yours
# ติดตั้ง: pip install numpy scikit-learn# ดาวน์โหลด sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์import numpy as np data = np.genfromtxt("radius-neighbors-sample.csv", delimiter=",", skip_header=1)X = data[:, :2] from sklearn.neighbors import RadiusNeighborsClassifierfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import MinMaxScalerfrom 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(MinMaxScaler(), RadiusNeighborsClassifier(radius=0.15, outlier_label=-1))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))print("Coverage:", np.mean(pred != -1))# -1 = ไม่มีเพื่อนบ้าน; accuracy รวมถือว่าการงดตอบไม่ถูกต้อง# Python fit scaler จากชุดฝึก; Playground ใช้พิกัดหาร 100# จึงอาจได้คำตอบต่างกันเล็กน้อย โค้ดเปลี่ยนตามค่าที่เลือกใน Playground · แต่ละภาษาใช้ข้อมูลตัวอย่างตามที่ระบุในโค้ด
05Learn by playing
ปรับ รัศมีบนพิกัด 0–1 แล้วสังเกตรูปแบบคำทำนายและตัวชี้วัดที่คำนวณใหม่ทันที
Playground เป็น implementation เพื่อการเรียนรู้ที่คำนวณในเบราว์เซอร์ ส่วนโค้ด Python ใช้ scikit-learn จึงอาจให้ค่าต่างกันเล็กน้อยจากรายละเอียดการ optimize และค่าเริ่มต้นของไลบรารี ผลนี้ไม่ใช่ benchmark บนข้อมูลจริง