[機器學習]特徵選取vs特徵萃取,應用breast_cancer資料庫比較其差異

2024/01/06閱讀時間約 18 分鐘

前言

本文會利用sklearn引入breast_cancer datasets來訓練,在處理數據的部份,特徵工程用兩種方式去做處理,分別是特徵選取特徵萃取的方式去做比較。

本文中特徵工程的差異

特徵選取的方法中,使用了KNN 分類器來選出最重要的兩個特徵

特徵萃取的方法中,使用了PCA降維到兩個特徵


實驗方法

資料集採用:breast_cancer datasets

模型採用 : LogisticRegression()邏輯斯回歸(二分類)

特徵工程:分三種,一、不做特徵工程,二、特徵選取,三、特徵萃取

實驗目的:分別比較其優劣


實驗結果

準確率 : 97.37%(不做特徵工程)> 95.61%特徵萃取(PCA) > 91.22%特徵選取(Knn-2)

做了PCA準確率還有95.61%,相比於不做特徵工程的狀況下,對於未知的資料可能預測能力會更好一些,畢竟不會受其他不重要的特徵影響。

30個特徵中挑出2個最重要的特徵來訓練預測,還有91.22%的準確率代表其他28的特徵的存在感是多麼薄弱

總結一下兩者的優缺點:

優點 :

  1. 減少計算時間:資料變小,當然就算得快
  2. 提高模型性能:模型的複雜度變簡單,性能自然就提高
  3. 去除冗餘信息:減少特徵數量可以去除這些冗餘信息,保留對目標變量影響較大的特徵

缺點:

資訊損失: 可能會將重要的資訊去除,或忽略掉相關聯的資料


科普資訊

特徵選取(Feature Selection)和特徵萃取(Feature Extraction)是機器學習中兩個不同的概念,用於處理數據的特徵(features)。以下是它們之間的主要差異:

  1. 特徵選取(Feature Selection):
    • 定義: 特徵選取是指從原始特徵集中選擇一個子集,而不對特徵進行修改或變換。選擇的特徵是原始特徵的子集,可能是原始特徵的某些子集或者所有特徵的一個子集。
    • 目的: 減少特徵維度,提高模型訓練的效率,去除冗餘或不重要的特徵,並避免過擬合。
    • 方法: 基於統計方法、信息論、機器學習模型的特徵重要性等,來選擇最具代表性的特徵。
  2. 特徵萃取(Feature Extraction):
    • 定義: 特徵萃取是指通過對原始特徵進行某種變換或轉換,創建新的特徵集。這種轉換通常是非線性的,可以將原始特徵映射到一個新的低維度空間。
    • 目的: 降低特徵維度,同時保留數據的最重要信息,提高模型的泛化能力,減少過度擬合的風險。
    • 方法: 常見的特徵萃取方法包括主成分分析(PCA)、獨立成分分析(ICA)、t-分佈隨機鄰域嵌入(t-SNE)等。

總體而言,特徵選取是在保留原始特徵的基礎上去除一些不必要的特徵,而特徵萃取則是通過轉換原始特徵來創建新的特徵


程式範例

breast cancer_class 特徵選取VS特徵萃取.ipynb

Streamlit web APP 乳癌預測

Streamlit web APP實現的程式範例會在下一篇文章有更多的介紹

此範例在Colab上實做,故採用.ipynb的方式呈現,以下將會分段介紹,如程式範例連結的順序說明。

載入相關套件

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

1. 載入資料集

ds = datasets.load_breast_cancer()

加載了乳腺癌資料集。這是一個常用的二元分類資料集,用於預測乳腺腫瘤是良性還是惡性

2. 資料清理、資料探索與分析

後面將採用pandas的方式來做這三步驟

查看資料夾說明

# 資料集說明
print(ds.DESCR)

返回的資料集是乳腺癌資料集,包含了30個特徵變數,用來預測乳腺腫瘤是良性(benign)還是惡性(malignant)。以下是該資料集的相關說明:

乳腺癌資料集(Breast Cancer Dataset)

特徵變數(Features):

  1. radius_mean: 平均半徑
  2. texture_mean: 平均紋理
  3. perimeter_mean: 平均周長
  4. area_mean: 平均面積
  5. smoothness_mean: 平均光滑度
  6. compactness_mean: 平均緊湊性
  7. concavity_mean: 平均凹度
  8. concave points_mean: 平均凹點
  9. symmetry_mean: 平均對稱性
  10. fractal_dimension_mean: 平均分形維度
  11. radius_se: 標準誤半徑
  12. texture_se: 標準誤紋理
  13. perimeter_se: 標準誤周長
  14. area_se: 標準誤面積
  15. smoothness_se: 標準誤光滑度
  16. compactness_se: 標準誤緊湊性
  17. concavity_se: 標準誤凹度
  18. concave points_se: 標準誤凹點
  19. symmetry_se: 標準誤對稱性
  20. fractal_dimension_se: 標準誤分形維度
  21. radius_worst: 最大半徑
  22. texture_worst: 最大紋理
  23. perimeter_worst: 最大周長
  24. area_worst: 最大面積
  25. smoothness_worst: 最大光滑度
  26. compactness_worst: 最大緊湊性
  27. concavity_worst: 最大凹度
  28. concave points_worst: 最大凹點
  29. symmetry_worst: 最大對稱性
  30. fractal_dimension_worst: 最大分形維度

目標變數(Target):

  • target: 0 表示良性(benign),1 表示惡性(malignant)

轉換為 DataFrame 格式

轉換成DataFrame的格式才方便後續去做資料探索及分析處理

import pandas as pd
df = pd.DataFrame(ds.data, columns=ds.feature_names)
df
df

df

查看目標值名稱

ds.target_names
目標值名稱

目標值名稱

觀察資料集彙總資訊

df.info()

目的是看資料集是否有缺失值,或每個特徵的數量是否都符合,及資料格式

raw-image

查詢是否有遺失值

都是0代表都沒遺失。

df.isnull().sum()
raw-image

特徵的描述統計量

這些統計量提供了對數據分布的基本了解

# 描述統計量
df.describe()
  • count: 樣本數量,表示每個特徵中非缺失值的數量。
  • mean: 平均值,表示每個特徵的平均數值。
  • std: 標準差,衡量數據值的分散程度。
  • min: 最小值,每個特徵中的最小數值。
  • 25%: 第一四分位數(Q1),25% 百分位數,表示數據的前 25% 範圍內的值。
  • 50%: 中位數(Q2),50% 百分位數,表示數據的中位數或第二四分位數。
  • 75%: 第三四分位數(Q3),75% 百分位數,表示數據的前 75% 範圍內的值。
  • max: 最大值,每個特徵中的最大數值。
describe

describe

探索了每個特徵的分布情況

察箱型圖,你可以得到以下資訊:

# 箱型圖
import seaborn as sns
sns.boxplot(data=df)

有箱型圖就可見,只有兩個特徵是可參考而已,其他特徵值相似度都太高無參考價值

raw-image

查看目標值好壞比例

若兩者差異太大,也會影響到訓練出來的結果

# y 各類別資料筆數統計
import seaborn as sns
sns.countplot(x=y)
# 以Pandas函數統計各類別資料筆數
pd.Series(y).value_counts()
#出現以下資料
# 1 357
# 0 212​


目標值好壞比例圖

目標值好壞比例圖

資料分割

因為許多機器學習庫(如 scikit-learn)通常接受 NumPy 陣列作為輸入而不是 DataFrame,所以要轉回Numpy的模式

# 指定X,並轉為 Numpy 陣列
X = df.values

# 資料分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2)

# 查看陣列維度
X_train.shape, X_test.shape, y_train.shape, y_test.shape

特徵縮放

標準化可以幫助模型更好地處理不同尺度的特徵,提高模型的性能和收斂速度。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)

fit_transform 用於學習訓練集的統計參數並同時進行轉換,而 transform 僅用於對數據進行轉換

選擇演算法

from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()

定義clf物件為邏輯斯模型

模型訓練

clf.fit(X_train_std, y_train)

評估

得知全部的三十個特徵一起訓練出的模型,去預測驗證集得到 98.25%的準確率

# 計算準確率
print(f'{accuracy_score(y_test, y_pred)*100:.2f}%')
# 98.25%

也有用混淆矩陣來評估,是把假的當成真的,還是真的當成假的

# 混淆矩陣圖
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt

disp = ConfusionMatrixDisplay(confusion_matrix=confusion_matrix(y_test, y_pred)
, display_labels=ds.target_names)
disp.plot()
plt.show()
混淆矩陣結果

混淆矩陣結果

模型佈署

訓練好的模型當然要存起來,因為有特徵縮放,也要將特徵縮放的參數一併儲存喔

# # 模型存檔
import joblib

joblib.dump(clf, 'model.breast')
joblib.dump(scaler, 'scaler.breast')

測試選取2個特徵的所有組合

測試所有的兩個特徵的組合,從中得到哪兩個特徵是最重要的

from sklearn.metrics import accuracy_score
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=11)
sfs = SequentialFeatureSelector(knn, n_features_to_select=2)
sfs.fit(X_train_std, y_train)
sfs.get_support()
  1. 建立 K 最近鄰分類器:
    • knn = KNeighborsClassifier(n_neighbors=11) 創建了一個 K 最近鄰分類器,其中 n_neighbors=11 表示選擇 11 個最近的鄰居進行分類。
  2. 建立 SequentialFeatureSelector:
    • sfs = SequentialFeatureSelector(knn, n_features_to_select=2) 創建了一個 SequentialFeatureSelector 物件,指定了要使用的分類器 knn 和要選擇的特徵數目 n_features_to_select=2。
  3. 擬合模型:
    • sfs.fit(X_train_std, y_train) 使用標準化後的訓練數據 X_train_std 和對應的目標變數 y_train 來擬合模型,進行特徵選擇。
  4. 取得選擇的特徵:
    • sfs.get_support() 這個方法返回一個布林陣列,指示每個特徵是否被選擇。如果特徵被選擇,對應的布林值為 True;否則,為 False。

印出選到的特徵

印出worst perimeter與worst smoothness 這兩個經過特徵選取的結果

# 特徵選取名稱
sfs.get_feature_names_out(column_list)
#印出結果# array(['worst perimeter', 'worst smoothness'], dtype=object)

資料轉換

將原先的訓練資料轉換成只有特徵選取的部分,順便印出形狀確認資料維度

sfs.transform(X_train_std).shap

模型訓練

因為資料轉換是由特徵縮放過得去轉換,所以這邊不用在做一次特徵縮放了

from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(sfs.transform(X_train_std), y_train)
clf.score(sfs.transform(X_test_std), y_test)

#得出結果 : 0.91

進行特徵萃取(PCA)

換做PCA的部分

# PCA 函數實作
def PCA_numpy(X, X_test, no):
cov_mat = np.cov(X.T)
# 計算特徵值(eigenvalue)及對應的特徵向量(eigenvector)
eigen_val, eigen_vecs = np.linalg.eig(cov_mat)
# 合併特徵向量及特徵值
eigen_pairs = [(np.abs(eigen_val[i]), eigen_vecs[:,i]) for i in range(len(eigen_vecs))]

# 針對特徵值降冪排序
eigen_pairs.sort(key=lambda x: x[0], reverse=True)

w = eigen_pairs[0][1][:, np.newaxis]
for i in range(1, no):
w = np.hstack((w, eigen_pairs[i][1][:, np.newaxis]))

# 轉換:矩陣相乘 (n, m) x (m, 2) = (n, 2)
return X.dot(w), X_test.dot(w)

X_train_pca, X_test_pca = PCA_numpy(X_train_std, X_test_std, 2) # 取 2 個特徵
X_train_pca.shape, X_test_pca.shape

這個PCA函數的目的是執行主成分分析(Principal Component Analysis),將原始的特徵空間轉換為更低維度的空間,同時保留數據集中的主要變異性。以下是對函數主要部分的解釋:

  1. cov_mat = np.cov(X.T): 計算輸入資料 X 的協方差矩陣,X.TX 的轉置。
  2. eigen_val, eigen_vecs = np.linalg.eig(cov_mat): 使用 np.linalg.eig 函數計算協方差矩陣的特徵值和特徵向量。
  3. eigen_pairs = [(np.abs(eigen_val[i]), eigen_vecs[:, i]) for i in range(len(eigen_vecs))]: 創建特徵值和特徵向量對的列表,其中 np.abs 用於取特徵值的絕對值。
  4. eigen_pairs.sort(key=lambda x: x[0], reverse=True): 將特徵值特徵向量對按照特徵值的降冪順序進行排序。
  5. w = eigen_pairs[0][1][:, np.newaxis]: 初始化 w,將第一個特徵向量轉為列向量。
  6. for i in range(1, no): w = np.hstack((w, eigen_pairs[i][1][:, np.newaxis])): 進行迴圈,將後續的特徵向量以列的形式附加到 w 中,以構建降維的轉換矩陣。
  7. return X.dot(w), X_test.dot(w): 使用 dot 方法進行矩陣相乘,將訓練數據和測試數據投影到所選擇的主成分上。
  8. 最後,X_train_pca.shape, X_test_pca.shape 返回降維後的訓練和測試數據的形狀。

這樣,`X_train_pca` 和 `X_test_pca` 就是降維後的訓練和測試數據,形狀為 `(樣本數, 主成分數)`。這有助於減少數據的維度,同時保持數據中的主要變異性。

scikit-learn 中已經提供了現成的 PCA 實現

也可以用這行來取代上方的範例,主要上方的範例是解釋其PCA的流程

from sklearn.decomposition import PCA

# 創建 PCA 實例,指定要保留的主成分數量
pca = PCA(n_components=2)

# 在訓練數據上擬合 PCA 模型,同時進行轉換
X_train_pca_sklearn = pca.fit_transform(X_train_std)
X_test_pca_sklearn = pca.transform(X_test_std)

# 查看降維後的數據形狀
X_train_pca_sklearn.shape, X_test_pca_sklearn.shape

模型訓練

from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
# 計算準確率
y_pred = clf.predict(X_test_pca)
print(f'{accuracy_score(y_test, y_pred)*100:.2f}%')
#印出結果​ 94.74%

若是喜歡的話,再按愛心及追蹤,希望分享對大家有所幫助





49會員
88內容數
Python程式設計師,不是在學習就是在學習的路上
留言0
查看全部
發表第一個留言支持創作者!