🗺️ AI 學習與考證地圖
中級科目三程式實戰 · 非監督分群

群號從 0 開始編,
那 labels 印出來的 -1 是誰?

DBSCAN 跟 K-means 最大的性格差異:它不強迫每個點都有家。夠密的地方長出群(編號 0、1、2⋯⋯),密度不夠、沒被任何群收編的落單點,被標成 -1——雜訊點(noise point)。本頁實跑「大海撈針」,一字不差重現題目印出的那 8 個數字,拆解核心點/邊界點/噪點三種身分,再把 -1 在下游的專屬紀律(群數要減 1、silhouette 會被拖累、還能拿來抓異常)一次講完。

閱讀模式

00題目

對標準化後資料執行 DBSCAN,取得部分分群標籤如下。其中 labels_ 或 fit_predict 結果中的 -1 代表什麼?

from sklearn.cluster import DBSCAN          # 拿出密度分群工具
db = DBSCAN(eps=0.45, min_samples=5)        # 半徑 0.45、鄰域至少 5 點(含自己)
labels = db.fit_predict(X_scaled)            # 分群,回傳每筆的標籤
print(labels[:8])                            # 印出前 8 筆
# [ 0  0 -1  1  1 -1  0  1]                   # ← 執行結果:有 0、有 1,還有兩個 -1

先說:-1 不是「第 -1 群」,是「不屬於任何群」

想像一個廣場,人群自然聚成一圈一圈聊天。DBSCAN 的規則很直白:站得夠密才算一圈——圈子依序編號 0、1、2⋯⋯。但廣場上總有幾個人站在空曠處,方圓幾步內湊不齊人、也沒有任何圈子把他拉進去。DBSCAN 不會硬把落單的人塞進最近的圈子(那是 K-means 的作風),而是給他們一個特別的記號:-1

所以讀 [ 0 0 -1 1 1 -1 0 1] 的正確方式是:第 1、2、7 筆在 0 號群,第 4、5、8 筆在 1 號群,第 3、6 筆不屬於任何群——它們是雜訊點。

兩個先立好的事實: 群號從 0 開始編——第一個群集叫「群 0」,不叫「群 1」,更不叫「群 -1」(這是選項 A 的死因)。 -1 是身分標記,不是群——數群數的時候它不能算進去,做評估的時候它要被特別對待(本頁 06 節)。

先點開看:雜訊點與 -1群號從 0 開始DBSCAN 是什麼?

不熟 AI 專有名詞?你需要先認識下列名詞

點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。

DBSCAN 的世界觀
DBSCANeps 鄰域半徑min_samples密度相連:群怎麼長大
三種身分
核心點邊界點雜訊點與 -1
標籤怎麼讀
群號從 0 開始群數怎麼數非監督學習
對照與陷阱
K-means 沒有 -1silhouette 與 -1拿 -1 抓異常缺值 NaN 會怎樣
這題的資料
為什麼是標準化後資料make_moons 月牙資料

不熟 Python?你需要先認識下列名詞

點擊後出現漸進式說明

把工具拿進來
importfrom模組 module
存東西、設數值
= 指派變數與命名關鍵字引數0.45 與 5:兩種數字
呼叫與取用
( ) 呼叫括號. 點運算子方法 method
讀輸出
[:8] 切片print# 註解與輸出行-1 這個數字本身

01逐行拆解:第 1 行到第 5 行

四行程式加一行輸出,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。

第 1 行拿出密度分群工具
from sklearn.cluster import DBSCAN   # 從「分群」抽屜拿出 DBSCAN

sklearn.cluster 是分群演算法的抽屜,上一頁的 KMeans 也住這裡。DBSCAN 的全名值得看一眼:Density-Based Spatial Clustering of Applications with Noise——名字的最後一個字就是 Noise(噪音)。它從演算法設計的第一天起,就打算把「不夠密的點」留在群外,而不是硬塞。

對照記憶:K-means 的世界觀是「每個點都必須有家」;DBSCAN 的世界觀是「夠密才成家,落單就標 -1」。這題考的就是這個世界觀。

相關名詞:DBSCAN模組 module非監督學習

第 2 行兩個參數,定義「什麼叫夠密」
db = DBSCAN(eps=0.45, min_samples=5)   # 半徑 0.45;圈內至少 5 點(含自己)才算核心

組一台 DBSCAN,貼上 db 的名牌。兩個參數就是它的全部人生觀:eps=0.45鄰域半徑——以每個點為圓心、0.45 為半徑畫圈;min_samples=5密度門檻——圈內至少要有 5 個點(sklearn 的數法含自己),這個點才夠格當「核心點」。

題目說資料是「標準化後」的,所以 0.45 的單位是標準差的尺度,不是原始單位——這正是上一頁(分群前為什麼要 StandardScaler)鋪好的路:eps 是帶單位的半徑,先縮放它才有意義。

這兩個參數聯手定義了「什麼叫夠密」——也就聯手決定了誰會被標 -1。本頁 05 節實跑給你看:同一份資料,參數一動,噪點可以從 0 筆變 300 筆。

相關名詞:eps 鄰域半徑min_samples為什麼是標準化後資料0.45 與 5:兩種數字

第 3 行fit_predict:分群,拿回標籤
labels = db.fit_predict(X_scaled)   # 對標準化資料分群,回傳每筆的標籤陣列

fit_predict 一口氣做完「找出所有核心點 → 把密度相連的核心點串成群 → 收編邊界點 → 剩下的標 -1」,回傳每一筆資料一個標籤的陣列。括號裡只有 X_scaled、沒有 y——分群是非監督學習,從頭到尾沒有人給過答案。

本站實跑:題目問「labels_ fit_predict 結果」——兩個是同一份東西。先 db.fit(X_scaled) 再讀 db.labels_,跟 fit_predict 的回傳值逐筆完全相同(np.array_equal → True)。一個是「做完順手給你」,一個是「事後從模型身上拿」,內容一字不差。

相關名詞:非監督學習方法 method. 點運算子

第 4 行印出前 8 筆看看
print(labels[:8])   # 切片:從頭取 8 筆,印出來

[:8] 是切片——「從頭取到第 8 筆」。labels 是 numpy 陣列,印出來的長相跟 Python 串列不同:沒有逗號、用空格對齊,所以才會長成 [ 0 0 -1 ...] 這種樣子——-1 佔兩個字元,正數前面就多墊一格,讓每個數字右對齊。看懂這個排版,考場上就不會被「奇怪的空格」分心。

相關名詞:[:8] 切片print

第 5 行輸出:兩個 -1 夾在群號裡
# [ 0  0 -1  1  1 -1  0  1]   # 註解行=把執行結果貼回程式裡

逐筆讀:第 1、2、7 筆 → 群 0;第 4、5、8 筆 → 群 1第 3、6 筆 → -1,不屬於任何群。光憑這 8 筆可以說「至少有 2 個群、至少有 2 個噪點」——注意是「至少」,後面 92 筆還沒看。

考場速讀法:看到 DBSCAN 的 labels 裡有 -1,直接翻譯成「這些點沒被納入任何密度群集,是雜訊點」——這就是選項 C 的原文。不要被「-1 排在 0 前面,會不會是第一群」帶走:第一群就叫 0,這行輸出裡自己就有。

相關名詞:# 註解與輸出行-1 這個數字本身群數怎麼數

02這 8 個數字是真的:大海撈針,重現題目

題目沒給 X_scaled 是什麼。為了讓每個數字都有出處,本站掃了上千組資料設定,找出一份「標準化後跑 DBSCAN(eps=0.45, min_samples=5)、印出來一字不差」的資料——最後在月牙資料 make_moons(n_samples=100, noise=0.22, random_state=1972) 撈到它:print(labels[:8]) 印出的正是 [ 0 0 -1 1 1 -1 0 1],連 numpy 的對齊空格都一樣。

重現用資料
100 筆
月牙資料,標準化後
DBSCAN 分出
4 群
群號 0~3(43/37/4/4 筆)
標成 -1 的雜訊點
12 筆
佔 12%——沒被任何群收編

把印出來的 8 筆逐一「驗明正身」——每一筆去數它 eps=0.45 圈內有幾個點(含自己):

印出順位印出的值eps 圈內點數(含自己)身分判定
第 1 筆077 ≥ 5 → 核心點,屬於群 0
第 2 筆077 ≥ 5 → 核心點,屬於群 0
第 3 筆-1雜訊點33 < 5、圈內也沒有核心點收編 → -1
第 4 筆177 ≥ 5 → 核心點,屬於群 1
第 5 筆11111 ≥ 5 → 核心點,屬於群 1
第 6 筆-1雜訊點33 < 5、圈內也沒有核心點收編 → -1
第 7 筆066 ≥ 5 → 核心點,屬於群 0
第 8 筆166 ≥ 5 → 核心點,屬於群 1
本站實跑(全部 100 筆):核心點 65、邊界點 23、雜訊點 12。那兩個 -1 的處境一模一樣:圈內只有 3 個點(含自己),離 5 的門檻差得遠;圈內僅有的 2 個鄰居也都不是核心點,沒有人能收編它們——於是被標 -1。順帶一提:印出的 8 筆裡有 6 筆是核心點,不是巧合——密的地方點本來就多,隨手抽都容易抽到核心點。
一個誠實的細節:eps=0.45 這個半徑配上標準化資料,能長出群的前提是資料夠稀疏(100 筆、noise 0.22)。同一組參數丟到 300 筆的密月牙上,所有點都互為鄰居,會全部黏成 1 群、0 個噪點(本站實跑掃過)——這預告了 05 節的主題:噪點名單是參數與密度共同決定的

03三種身分:核心點、邊界點、雜訊點

DBSCAN 給每個點的身分判定只有兩條規則,依序問:

第一問:以你為圓心、eps 為半徑畫圈,圈內點數(含自己)≥ min_samples 嗎?是 → 你是核心點,有資格長群、也有資格收編別人。
第二問(第一問沒過才問):你的圈內有沒有任何核心點?有 → 你被收編為邊界點,拿那個核心點所屬群的正常群號;沒有 → 你是雜訊點,標 -1

互動實驗室:身分判定器拉點數、切門檻,看身分怎麼翻
eps 圈內點數(含自己) 3 點
min_samples(核心門檻)
圈內有沒有核心點願意收編你?
三種身分的實跑範例(300 筆乾淨月牙、eps=0.25、min_samples=5):第 57 筆圈內有 16 點 → 核心點;第 19 筆圈內只有 2 點(含自己),但僅有的那位鄰居是核心點 → 被收編為邊界點,拿正常群號;第 98 筆圈內只有自己 1 個點,無人收編 → -1。全體:核心 270、邊界 23、噪點 7。
考點細節:min_samples 的數法「含自己」。sklearn 文件明寫 neighborhood 的點數包含該點本身——所以 min_samples=5 的意思是「自己+至少 4 個夠近的鄰居」。考題若問「min_samples=5 表示鄰居至少要 5 個」,那是不含自己的錯誤數法。另外:邊界點拿的是正常群號——labels 陣列裡看不出核心與邊界的差別(要看 db.core_sample_indices_),只有噪點拿 -1

相關名詞:核心點邊界點雜訊點與 -1密度相連

04把 -1 畫出來:月牙實跑三視圖

換一份 300 筆的乾淨月牙(DBSCAN 的主場:兩道彎彎的弧,K-means 最怕的形狀),標準化後用 eps=0.25, min_samples=5 實跑。同一批點、三種視角切換著看:

互動實驗室:月牙三視圖DBSCAN 結果、三種身分、K-means 對照
DBSCAN 對真實月牙的 ARI
0.955
2 群(144/149 筆)+ 7 噪點
K-means(k=2)的 ARI
0.479
直線切一刀,月牙被腰斬
那 7 個噪點在 K-means 裡
3+4
照樣被硬塞進群 0 與群 1
對照的重點不是誰分得好,是「世界觀」:K-means 的 labels 取值只有 {0, 1}——它的字典裡沒有 -1,每一筆(包含 DBSCAN 認定的 7 個噪點)都被分給最近的質心。DBSCAN 則誠實回報:「這 7 筆我不確定,不硬塞。」看到 labels 裡有 -1,本身就是「這是密度分群家族」的簽名——考題拿這個當辨識特徵考過不只一次。

相關名詞:K-means 沒有 -1make_moons 月牙資料

05噪點不是資料的屬性,是參數的函數

「這筆資料是不是雜訊點」不是寫在資料裡的事實——它是 epsmin_samples 畫出來的。同一份 300 筆月牙,把兩個旋鈕都掃一遍(本站實跑 20 組):

互動實驗室:eps × min_samples 掃描同一份資料,噪點從 0 筆到 300 筆
eps(鄰域半徑)
min_samples(密度門檻)
分出幾群
2
-1 不算群
標成 -1 的筆數
7
共 300 筆
噪點比例
2.3%
= 噪點 ÷ 300
掃描的兩個極端值得背下來:eps=0.15, min_samples=20 門檻嚴到沒人及格——300 筆全部標 -1、0 個群eps=0.50, min_samples=5 半徑大到人人都是鄰居——1 群、0 個噪點(連兩個月牙都被黏在一起)。同一份資料,噪點數從 0 到 300 都做得出來。所以「-1 代表什麼」的精確答案是:在這組 eps 與 min_samples 定義的密度下,該點未被納入任何密度群集——參數換了,名單就換。

06-1 的下游紀律:數群、計數、評估都要特別對待

-1 混在 labels 裡,下游每一步都要記得它「不是群」。三個最常踩的坑,配上標準寫法:

import numpy as np                                     # 拿出陣列工具
print(np.unique(labels, return_counts=True))           # 每種標籤各幾筆:-1 有 12 筆
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)  # 數群數:-1 要扣掉
mask = labels != -1                                    # 真假名單:非噪點為 True
X_kept = X_scaled[mask]                                # 只留被分到群的點做後續分析
互動實驗室:含著 -1 硬算 vs 先把 -1 挑出來群數與 silhouette 的差別
群數的讀法
3
len(set(labels)) 直接數
silhouette 分數
0.265
把 -1 當成一個「群」算
-1 在算式裡的角色
冒充群
7 個散兵被當成同一群
本站實跑(300 筆月牙、2 群+7 噪點):silhouette 含著 -1 算是 0.265,先用 mask = labels != -1 排除後是 0.384——-1 那「群」的成員散落四處,硬算會把整體分數拖低。群數也一樣:len(set(labels)) 數出 3({0, 1, -1}),但真正的群只有 2 個。
-1 的正面用法:「密度不夠、不屬於任何群」的點,天生就是離群值候選人——一行 outliers = X_scaled[labels == -1] 就撈出異常名單(實跑 7 筆)。這是 DBSCAN 常被拿來做異常偵測的原因。但要記得 05 節的教訓:名單是參數畫出來的,調一下 eps 名單就變,撈出來仍要人工檢視。

相關名詞:群數怎麼數silhouette 與 -1拿 -1 抓異常

07四個選項收工

A第一個群集的正式編號差一號

群號從 0 開始編——第一個群集的正式編號是 0,題目印出的 8 個數字裡自己就有。-1 被刻意選在群號序列(0, 1, 2, ⋯)之外,正是為了讓「不屬於任何群」在數字上一眼可辨。實跑重現的資料共 4 群,編號 0~3,-1 從頭到尾不在編號隊伍裡。

B該筆樣本遺失了一個特徵值根本進不了門

缺值連 DBSCAN 的門都進不了——本站實跑把一筆資料改成 NaN 再餵進去,直接拋出 ValueError: Input X contains NaN. DBSCAN does not accept missing values(sklearn 原文),根本輪不到輸出 labels。-1 說的是「位置太空曠」,不是「資料缺了格」——缺值要在前處理階段先補或先刪。

C該點未被納入任何密度群集,被標記為雜訊點正確

實跑逐字驗證:那兩個 -1 的點,eps=0.45 圈內只有 3 個點(含自己)湊不滿 min_samples=5——當不成核心點;圈內也沒有任何核心點的鄰域覆蓋它們——當不成邊界點;於是被標 -1:未被納入任何密度群集的雜訊點。這是 DBSCAN 的設計哲學:夠密才成群,落單不硬塞。

D演算法已知真實標籤為負類世界觀錯誤

分群是非監督學習——fit_predict(X_scaled) 括號裡只有 X 沒有 y,演算法從頭到尾不知道任何「真實標籤」,自然談不上「已知」;「負類(negative class)」更是監督式分類的詞彙,用在分群身上是張冠李戴。DBSCAN 的 -1 是幾何與密度的判定,跟任何真值無關。

回到題目:現在再作答一次

再看一次同一段程式。這次你知道那兩個 -1 經歷了什麼了。

對標準化後資料執行 DBSCAN,取得部分分群標籤如下。其中 labels_ 或 fit_predict 結果中的 -1 代表什麼?

db = DBSCAN(eps=0.45, min_samples=5)   # 「夠密」的定義:半徑 0.45 內至少 5 點(含自己)
labels = db.fit_predict(X_scaled)       # 分群(labels_ 與回傳值完全相同)
print(labels[:8])                       # 印前 8 筆
# [ 0  0 -1  1  1 -1  0  1]              # 群 0、群 1⋯⋯與兩個不屬於任何群的 -1

08自我檢測

八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。

09重點整理

  1. 五行程式的分工:第 1 行拿工具、第 2 行設「夠密」的定義(eps=0.45、min_samples=5)、第 3 行 fit_predict 分群拿標籤、第 4 行印前 8 筆、第 5 行是貼回來的輸出。fit_predict(X_scaled)只有 X 沒有 y——分群是非監督學習。
  2. 正確答案 C:-1 代表該點未被納入任何密度群集,被標記為雜訊點。實跑重現:那兩個 -1 的 eps 圈內只有 3 點(含自己)湊不滿 5、圈內也沒有核心點收編。
  3. 群號從 0 開始編(A 的死因):第一個群集叫「群 0」,輸出裡自己就有;-1 刻意選在群號序列之外,代表「不屬於任何群」。
  4. 三種身分的判定順序:圈內點數(含自己)≥ min_samples → 核心點;否則圈內有核心點 → 邊界點(拿正常群號);否則 → 雜訊點 -1。labels 裡看不出核心與邊界的差別,只有噪點拿 -1
  5. 實跑重現題目:make_moons(100 筆, noise=0.22, random_state=1972) 標準化後跑 DBSCAN(0.45, 5) → 一字不差印出 [ 0 0 -1 1 1 -1 0 1];全體 4 群(43/37/4/4)+ 12 噪點;核心 65、邊界 23、噪點 12。
  6. 噪點是參數的函數,不是資料的屬性:同一份 300 筆月牙,eps=0.15+min_samples=20 → 300 筆全是 -1;eps=0.50 → 0 個噪點、全黏成 1 群。「-1 名單」隨參數重畫。
  7. K-means 對照(世界觀差異):K-means 把每一筆都分給最近的質心——labels 只有 {0, 1},沒有 -1 這種東西;DBSCAN 的 7 個噪點在 K-means 裡照樣被硬塞(3+4 筆)。月牙形狀上 DBSCAN ARI 0.955、K-means 只有 0.479。
  8. 下游紀律一:數群要扣 -1——len(set(labels)) - (1 if -1 in labels else 0);直接 len(set) 會把 {0, 1, -1} 數成 3 群。
  9. 下游紀律二:評估先排除 -1——silhouette 含著 -1 算 0.265、用 mask = labels != -1 排除後 0.384;-1 那「群」成員四散,硬算會拖低分數。
  10. -1 的正面用法labels == -1 一行撈出離群候選名單——DBSCAN 兼職異常偵測的原因;但名單隨參數變,撈出後仍要檢視。
  11. B 的死因:缺值 NaN 根本進不了 DBSCAN——實跑直接 ValueError: Input X contains NaN,輪不到輸出 -1。D 的死因:非監督沒有「真實標籤」,「負類」是監督式分類的詞彙。
  12. labels_ 與 fit_predict 回傳完全相同(實跑 np.array_equal → True)——題目「labels_ 或 fit_predict 結果」兩種問法是同一件事。
完整程式碼