假設腫瘤(tumors)的資料如下:
Total: 100
Total positive: 9 (9%)
Total negative: 91 (91%)
在不平衡數據集中,僅僅依賴準確率(accuracy)來評估模型性能是不夠精確的
偏向多數類別:
在不平衡數據集中,多數類別樣本數遠多於少數類別。由於模型在訓練過程中接觸到的多數類別樣本更多,它更有可能傾向於預測多數類別。這樣,即使模型大部分時間都預測為多數類別,也會有很高的準確率。
例如,在例子中,假設模型總是預測“沒有腫瘤”,那麼模型的準確率會是91%,但這個模型對於檢測腫瘤的病人(正類別)的能力非常差。
忽略少數類別的性能:
高準確率可能掩蓋了模型對少數類別(如腫瘤病人)的差勁表現。由於少數類別樣本數少,模型即使預測錯誤,也不會顯著影響整體準確率,但這種錯誤對實際應用影響很大,特別是在醫療診斷中。
模型效果的錯誤認知:
僅看準確率,容易對模型的實際效果產生錯誤認知,導致模型在應用中表現不佳,特別是當我們更關心少數類別的預測時。
EX:
實際類別 預測類別 數量
Positive Positive 1
Positive Negative 8
Negative Positive 0
Negative Negative 91
準確率:模型預測的準確率是92%,這看起來非常高,給人一種模型表現良好的錯覺。
精確率:模型對於預測為正類(腫瘤)的樣本中,實際為正類的比例是100%,因為模型只預測了1個腫瘤,且這個預測是正確的。
召回率:模型對於實際為正類(腫瘤)的樣本中,僅正確預測了11.1%,這表示模型對於腫瘤的檢測能力非常差。
實際應用影響
在醫療診斷中,如果模型不能有效地檢測出腫瘤患者(即召回率低),那麼即使模型的整體準確率很高,這個模型在實際應用中仍然是不可靠的。漏診的腫瘤患者(8個漏診的樣本)可能會錯過及時治療的機會,對病人健康造成嚴重影響
# Change Classification Metrics
𝑃_𝑜𝑏𝑠𝑒𝑟𝑣𝑒𝑑: Probability of observed agreement (ie. the overall accuracy of the model)
𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒: Probability of agreement by chance (ie. the measure of the agreement between the model predictions and the actual class values as if happening by chance)
Kappa coefficient:
𝜅=(𝑃_𝑜𝑏𝑠𝑒𝑟𝑣𝑒𝑑−𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒)/(1−𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒 )
Ex:
TP:1 FP:1
FN:8 TN:90
Total=100
TP:1/100=0.01
TN:90/100=0.9
FN:8/100=0.08
FP:1/100=0.01
𝑃_𝑜𝑏𝑠𝑒𝑟𝑣𝑒𝑑= 0.01 + 0.9 = 0.91
𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒= 0.02(TP+FP) * 0.09(TP+FN) + 0.98(FN+TN) * 0.91(FP+TN) = 0.8936
𝜅=(𝑃_𝑜𝑏𝑠𝑒𝑟𝑣𝑒𝑑−𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒)/(1−𝑃_𝑏𝑦𝑐ℎ𝑎𝑛𝑐𝑒 )
= (0.91 −0.8936)/(1 −0.8936)
= 0.0164/0.1064=15.41%
Kappa系數(κ)用於衡量分類模型的預測結果和實際觀察結果之間的一致性,其值範圍通常在[−1,1] 之間。Kappa系數越接近1,表示模型預測結果和實際觀察結果的一致性越高,即模型效果越好
若資料集越 balanced, 𝜅 的值越大
# Resampling Dataset
**Over-sampling**
sampling with replacement :
把樣本從母體抽樣出來之後,再放回母體給下次的抽樣。意思就是同一筆資料可能出現多次。
SMOTE :
該算法通過選擇兩個或多個相似的樣本(使用距離度量)來生成合成樣本,並在不同屬性上通過隨機數量的擾動來產生新的樣本。如何選擇鄰居以及計算距離是改進這種算法的新方向。
1.選擇少數類別樣本:從少數類別中選擇一個樣本𝑥𝑖。
2.尋找鄰居:在少數類別中使用距離度量(如歐幾里得距離)找到該樣本的k個最近鄰居。
3.生成合成樣本:對於每個選定的鄰居樣本𝑥𝑖,𝑛𝑒𝑖𝑔ℎ𝑏𝑜𝑟,生成一個合成樣本。具體步驟如下:
* 對於樣本的每個屬性,計算兩個樣本間的差異。
* 將這個差異乘以一個介於0和1之間的隨機數,再加上原樣本的屬性值。
* 這樣,每個屬性就被隨機擾動了一個值,產生了一個新的合成樣本。
假設我們有一個少數類別樣本𝑥𝑖和它的鄰居𝑥𝑖,𝑛𝑒𝑖𝑔ℎ𝑏𝑜𝑟,它們在兩個特徵空間中的表示如下:
𝑥𝑖=(2,3)
𝑥𝑖,𝑛𝑒𝑖𝑔ℎ𝑏𝑜𝑟=(4,7)
為生成一個合成樣本,我們可以按以下步驟進行:
計算每個屬性的差異:
差異 =𝑥𝑖,𝑛𝑒𝑖𝑔ℎ𝑏𝑜𝑟−𝑥𝑖=(4−2,7−3)=(2,4)
對每個屬性乘以一個隨機數(假設為 0.5):
擾動值 =(2∗0.5,4∗0.5)=(1,2)
將擾動值加到原樣本上:
新的合成樣本 =𝑥𝑖+擾動值=(2+1,3+2)=(3,5)=x
因此,新的合成樣本為 (3,5)。
選擇鄰居和計算距離是改進SMOTE算法的關鍵方向
1.距離度量的選擇:
歐幾里得距離:最常用的距離度量,但對於高維數據和非線性數據效果較差。
曼哈頓距離:對於一些特定的數據分佈可能更有效。
馬氏距離:考慮了數據分佈的共變異數矩陣,對高維數據更穩定。
余弦相似度:適用於文本數據等高維稀疏數據。
2.鄰居選擇的策略:
隨機選擇:傳統SMOTE中隨機選擇k個鄰居,但可能包含噪聲數據。
聚類選擇:通過聚類方法(如KMeans)選擇同一聚類中的鄰居,減少噪聲影響。
加權選擇:根據樣本的重要性或相似性給予鄰居不同的權重。
**Under-sampling**
範例程式碼:
```
import random
import numpy as np
import pandas as pd
from imblearn.over_sampling import RandomOverSampler, SMOTE
from imblearn.under_sampling import RandomUnderSampler
from collections import Counter
import seaborn as sns
import matplotlib.pyplot as plt
# 設定隨機種子,使每次產生的資料相同
random.seed(2022)
np.random.seed(2022)
n1 = 200 # 第一類的數量(type1)
n2 = 20 # 第二類的數量(type2)
# 產生type1的data
x = np.random.normal(0, 0.5, n1)
y = np.random.normal(0, 1, n1)
data1 = pd.DataFrame({'label': ['type1'] * n1, 'x': x, 'y': y})
# 產生type2的data
x2 = np.random.normal(2, 0.5, n2)
y2 = np.random.normal(2, 1, n2)
data2 = pd.DataFrame({'label': ['type2'] * n2, 'x': x2, 'y': y2})
# 建立 imbalanced_data
imbalance_data = pd.concat([data1, data2])
print(imbalance_data['label'].value_counts())
# type1 200
# type2 20
```
```
# 使用 RandomOverSampler 進行過採樣
oversample = RandomOverSampler(sampling_strategy='minority')
X_over, y_over = oversample.fit_resample(imbalance_data[['x', 'y']], imbalance_data['label'])
balanced_data_over = X_over
balanced_data_over['label'] = y_over
print(Counter(balanced_data_over['label']))
# Counter({'type1': 200, 'type2': 200})
```
```
# 使用 SMOTE 進行過採樣
X_smote, label_smote = SMOTE(random_state=2022).fit_resample(imbalance_data[['x', 'y']], imbalance_data['label'])
balanced_data_smote = X_smote
balanced_data_smote['label'] = label_smote
print(Counter(balanced_data_smote['label']))
# Counter({'type1': 200, 'type2': 200})
```
```
# 使用 RandomUnderSampler 進行欠採樣
undersample = RandomUnderSampler(sampling_strategy='majority')
X_under, y_under = undersample.fit_resample(imbalance_data[['x', 'y']], imbalance_data['label'])
balanced_data_under = X_under
balanced_data_under['label'] = y_under
print(Counter(balanced_data_under['label']))
# Counter({'type1': 20, 'type2': 20})
```
```
# 繪圖
fig, axs = plt.subplots(ncols=4, figsize=(24, 6))
sns.scatterplot(data=imbalance_data, x='x', y='y', hue='label', ax=axs[0]).set(title='Imbalance data')
sns.scatterplot(data=balanced_data_over, x='x', y='y', hue='label', ax=axs[1]).set(title='Balanced data (RandomOverSampler)')
sns.scatterplot(data=balanced_data_smote, x='x', y='y', hue='label', ax=axs[2]).set(title='Balanced data (SMOTE)')
sns.scatterplot(data=balanced_data_under, x='x', y='y', hue='label', ax=axs[3]).set(title='Balanced data (RandomUnderSampler)')
plt.show()
```

