特徵工程就是一個把原始數據轉變成特徵的過程,這些特徵可以很好的描述這些數據,並且利用它們建立的模型在未知數據上的表現性能可以達到最優(或者接近最佳性能)。
一般來說,機器學習專案的優化有兩個角度:「更厲害的模型」跟「更好用的資料」。所謂的特徵工程就是從「更好用的資料」作為目的進行資料處理。
資料跟特徵決定模型的上限,模型跟算法只是逼近這個上限。
# 可使用目錄功能快速確認要閱覽的主題
特徵工程是機器學習中非常重要的一個步驟,它是指對原始數據進行轉換和處理,以提取出對機器學習模型有意義的特徵,常見的方法包括異常值檢測、特徵轉換、特徵縮放、特徵表示、特徵選擇和特徵提取。
在特徵工程中,異常值(outliers)的檢測是非常重要的一步。異常值可能會影響模型的表現,因此在建模前對異常值進行識別和處理是必要的。下面介紹幾種常見的異常值檢測方法,並展示如何使用 Python 來實現這些方法。
Z-Score 用於衡量一個數據點與均值的距離,通常在±3以外的數據被認為是異常值。
import numpy as np
import pandas as pd
# 假設有一個數據集 df
df = pd.DataFrame({
'feature1': [1, 2, 3, 4, 5, 6, 7, 100, 9, 10, 11, 12]
})
# 計算Z-Score
df['z_score'] = (df['feature1'] - df['feature1'].mean()) / df['feature1'].std()
# 找到異常值
outliers = df[df['z_score'].abs() > 3]
print(outliers)
"""
feature1 z_score
7 100 3.148021
"""
Z-Score 詳細介紹:
IQR 是 Q3(75%分位數)與 Q1(25%分位數)之間的距離,通常定義超過1.5倍IQR的數據為異常值。
import numpy as np
import pandas as pd
# 假設有一個數據集 df
df = pd.DataFrame({
'feature1': [1, 2, 3, 4, 5, 6, 7, 100, 9, 10, 11, 12]
})
# 計算Q1和Q3
Q1 = df['feature1'].quantile(0.25)
Q3 = df['feature1'].quantile(0.75)
IQR = Q3 - Q1
# 找到異常值
outliers = df[(df['feature1'] < (Q1 - 1.5 * IQR)) | (df['feature1'] > (Q3 + 1.5 * IQR))]
print(outliers)
"""
feature1
7 100
"""
Isolation Forest 是一種基於樹模型的異常值檢測方法,通過將異常值隔離來進行檢測。
from sklearn.ensemble import IsolationForest
import numpy as np
import pandas as pd
# 假設有一個數據集 df
df = pd.DataFrame({
'feature1': [1, 2, 3, 4, 5, 6, 7, 100, 9, 10]
})
# 創建模型
iso = IsolationForest(contamination=0.1, random_state=42) # contamination表示預期異常值的比例
# 訓練模型
df['anomaly'] = iso.fit_predict(df[['feature1']])
# 找到異常值
outliers = df[df['anomaly'] == -1]
print(outliers)
"""
feature1 anomaly
7 100 -1
"""
contamination
:這個參數指定了數據集中異常值的大約比例。在這裡設置為 0.1
,即預期有 10% 的數據是異常值。根據你的數據集的性質,你可以調整這個值。random_state
:為了確保結果的可重複性,可以設置一個隨機種子。predict
:這個方法對每個數據點進行分類,異常值標記為 -1
,正常值標記為 1
。Isolation Forest 的基本原理是異常點(離群點)比正常點更容易被隔離。具體來說,它的工作方式如下:
使用可視化工具,如箱型圖(Box Plot)或小提琴圖,可以直觀地查看數據的分布及其異常值。
import seaborn as sns
import matplotlib.pyplot as plt
# 創建一個包含潛在異常值的數據集
df = pd.DataFrame({
'feature1': [1, 2, 3, 4, 5, 6, 7, 100, 9, 10] # 100 是潛在異常值
})
# 繪製箱型圖
sns.boxplot(x=df['feature1'])
plt.show()
將特徵轉換為另一種形式,通常是為了讓數據更適合模型的假設或使數據更具可解釋性。特徵轉換的主要目的是通過改變數據的分佈形狀、處理非線性關係、或減少數據的偏態和峰態,來提高模型的性能。
對數轉換可以將數據的分佈從右偏(右側的長尾)轉換為更接近正態分佈。這有助於模型更好地處理數據,特別是在數據中存在極端值或長尾的情況下。
範例: 假設你有一個房價數據集,其中房價這個特徵的分佈是右偏的:
import numpy as np
import pandas as pd
# 假設有一個房價數據集
df = pd.DataFrame({
'Price': [100000, 150000, 200000, 250000, 300000, 350000, 400000, 450000, 1000000]
})
# 對數轉換
df['LogPrice'] = np.log(df['Price'])
print(df)
"""
Price LogPrice
0 100000 11.512925
1 150000 11.918391
2 200000 12.206073
3 250000 12.429216
4 300000 12.611538
5 350000 12.765688
6 400000 12.899220
7 450000 13.017003
8 1000000 13.815511
"""
結果: 對數轉換後,LogPrice
列的分佈會變得更加均勻,極端值對數據的影響也會減少。
平方根轉換通常用於減少數據的偏態,尤其適合處理數據中存在零值或小範圍數據的情況。這種轉換可以讓數據分佈更加平滑。
範例: 假設有一個數據集記錄了城市人口數量,並且數據的分佈是右偏的:
# 人口數據集
df = pd.DataFrame({
'Population': [100, 150, 200, 300, 500, 800, 1000, 5000, 10000]
})
# 平方根轉換
df['SqrtPopulation'] = np.sqrt(df['Population'])
print(df)
"""
Population SqrtPopulation
0 100 10.000000
1 150 12.247449
2 200 14.142136
3 300 17.320508
4 500 22.360680
5 800 28.284271
6 1000 31.622777
7 5000 70.710678
8 10000 100.000000
"""
結果: SqrtPopulation
會顯示轉換後的數據,使得分佈更加平衡。
Box-Cox 轉換是一種更靈活的轉換方法,可以自動找到最佳的轉換參數 λ,使數據接近正態分佈。這對於任何類型的偏態分佈數據都有效。
範例:
from scipy import stats
# 假設有一組非正態分佈的數據
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# Box-Cox 轉換
transformed_data, best_lambda = stats.boxcox(data)
print("Transformed Data:", transformed_data)
print("Best Lambda:", best_lambda)
"""
Transformed Data: [0. 0.89952679 1.67649212 2.38322967 3.04195194 3.66477652
4.25925123 4.83048782 5.38215513 5.91700147]
Best Lambda: 0.7219640728650736
"""
結果: Box-Cox 轉換後的數據會更加接近正態分佈,best_lambda
是最佳的轉換參數。
將數據縮放到均值為 0,標準差為 1 的範圍。這在需要比較不同量級的特徵時特別有用,例如在使用線性回歸或支持向量機等模型時。
範例:
from sklearn.preprocessing import StandardScaler
# 假設有兩個特徵,量級不同
df = pd.DataFrame({
'Height': [150, 160, 170, 180, 190],
'Weight': [50, 60, 70, 80, 90]
})
# 標準化
scaler = StandardScaler()
df[['Height', 'Weight']] = scaler.fit_transform(df[['Height', 'Weight']])
print(df)
"""
Height Weight
0 -1.414214 -1.414214
1 -0.707107 -0.707107
2 0.000000 0.000000
3 0.707107 0.707107
4 1.414214 1.414214
"""
結果: Height
和 Weight
特徵會被縮放到相同的範圍內,使它們在模型中不會因量級差異而產生偏差。
將現有特徵升高到某一冪次,以捕捉非線性關係。這對於線性模型在處理非線性數據時非常有用。
範例:
from sklearn.preprocessing import PolynomialFeatures
# 假設有一個特徵
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1)
# 生成二次多項式特徵
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
print(X_poly)
"""
[[ 1. 1. 1.]
[ 1. 2. 4.]
[ 1. 3. 9.]
[ 1. 4. 16.]
[ 1. 5. 25.]]
"""
結果: 輸出將包括原始特徵、一階特徵(即原特徵)和二次特徵,這些新特徵可以捕捉到數據中的非線性關係。
用於將數據擴展到更大的範圍,這在數據中存在小數值並希望放大這些數值的影響時非常有用。
# 假設有一組數據
df = pd.DataFrame({
'Value': [1, 2, 3, 4, 5]
})
# 指數轉換
df['ExpValue'] = np.exp(df['Value'])
print(df)
"""
Value ExpValue
0 1 2.718282
1 2 7.389056
2 3 20.085537
3 4 54.598150
4 5 148.413159
"""
結果: ExpValue
將顯示指數轉換後的數據,這些數值將比原始數據增長得更快。
特徵縮放是指將數據縮放到一個固定的範圍內,以使所有特徵具有相同的重要性。特徵縮放通常用於需要計算距離的機器學習算法中,例如 k-最近鄰(k-nearest neighbors,KNN)和支持向量機(support vector machine,SVM)。以下是一些常見的特徵縮放方法。
標準化適合用於特徵值範圍差異大且模型對特徵的尺度敏感的情況,如支持向量機(SVM)、K最近鄰(KNN)等算法。在線性回歸中,如果特徵範圍不同,標準化有助於提高模型的收斂速度。
z = (x - mean) / std
,其中 x
是原始數據,mean
是均值,std
是標準差。範例:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
標準化適用場合:
標準化優點:
標準化缺點:
最小最大縮放適合用於需要將數據映射到特定範圍內的情況,如神經網絡中要求數據輸入在特定範圍內。在特徵範圍已知且數據分佈相對均勻的情況下使用。
x_scaled = (x - min) / (max - min)
,其中 min
和 max
是特徵的最小值和最大值。範例:
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
最小最大縮放適用場合:
最小最大縮放優點:
最小最大縮放缺點:
最大絕對值縮放適合用於稀疏數據(例如有很多0值的數據),如文本數據表示的稀疏矩陣。
x_scaled = x / max_abs(x)
,其中 max_abs(x)
是特徵的最大絕對值。範例:
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
X_scaled = scaler.fit_transform(X)
最大絕對值縮放適用場合:
最大絕對值縮放優點:
最大絕對值縮放缺點:
均值縮放適合用於數據分佈不均勻且希望中心化數據時。在進行線性模型建模時,用於平衡特徵值的尺度。
x_scaled = (x - mean) / (max - min)
。範例:
X_scaled = (X - X.mean()) / (X.max() - X.min())
均值縮放適用場合:
均值縮放優點:
均值縮放缺點:
魯棒縮放使用於當數據包含異常值且這些異常值對模型性能有負面影響時。適合於數據中存在大量離群點的情況。
x_scaled = (x - median) / IQR
。範例:
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)
魯棒縮放適用場合:
魯棒縮放優點:
魯棒縮放缺點:
取對數適合用於數據具有較強右偏態(如收入、價格等),對數轉換能使數據分佈更加對稱,有助於改善模型性能。於處理極值影響較大的數據,取對數可以減少異常值對模型訓練的干擾。
y = log(x)
,其中 x
是原始數據,y
是取對數後的數據。範例:
import numpy as np
import pandas as pd
# 假設有一個數據集 df,包含收入數據
df = pd.DataFrame({
'Income': [1000, 1200, 1500, 3000, 10000]
})
# 對收入數據進行取對數轉換
df['Income_log'] = np.log(df['Income'])
取對數適用場合:
取對數優點:
取對數缺點:
正規化通常用於需要保證數據特徵之間的比例一致,特別是使用正規化回歸模型(如Ridge和Lasso回歸)時。適合於數據分佈範圍相差懸殊,且這些範圍差異會影響模型性能的情況。
方法:將數據縮放到一個固定範圍內,通常是 [0, 1] 或 [-1, 1],使不同特徵的值處於相同的範圍內。
公式:
最小-最大正規化(Min-Max Normalization):x_normalized = (x - x_min) / (x_max - x_min)
,其中 x
是原始數據,x_min
是最小值,x_max
是最大值。
範例:
from sklearn.preprocessing import Normalizer
normalizer = Normalizer()
X_normalized = normalizer.fit_transform(X)
正規化適用場合:
正規化優點:
正規化缺點:
所使用的算法
在特徵工程中,特徵表示(Feature Representation)是將原始數據轉換成模型可以理解和使用的形式的過程。以下是一些常用的特徵表示方法。
範例:
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# 假設有一個滿意度的數據集
data = {'satisfaction': ['low', 'medium', 'high', 'low', 'medium', 'high']}
df = pd.DataFrame(data)
# 使用 LabelEncoder 進行編碼
label_encoder = LabelEncoder()
df['satisfaction_encoded'] = label_encoder.fit_transform(df['satisfaction'])
print(df)
"""
satisfaction satisfaction_encoded
0 low 1
1 medium 2
2 high 0
3 low 1
4 medium 2
5 high 0
"""
延伸閱讀:
【資料分析】python資料處理-類別欄位轉換基礎操作語法彙整
範例:
import pandas as pd
data = {'color': ['red', 'blue', 'green', 'blue', 'red']}
df = pd.DataFrame(data)
df_one_hot = pd.get_dummies(df['color'])
print(df_one_hot)
"""
blue green red
0 0 0 1
1 1 0 0
2 0 1 0
3 1 0 0
4 0 0 1
"""
延伸閱讀:
【資料分析】python資料處理-類別欄位轉換基礎操作語法彙整
範例:
from sklearn.feature_extraction.text import CountVectorizer
documents = ["I love data science", "Data science is awesome", "I love Python"]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(documents)
print(X.toarray())
print(vectorizer.get_feature_names_out())
"""
[[0 1 0 1 0 1]
[1 1 1 0 0 1]
[0 0 0 1 1 0]]
['awesome' 'data' 'is' 'love' 'python' 'science']
"""
範例:
from gensim.models import Word2Vec
sentences = [["I", "love", "data", "science"], ["Data", "science", "is", "awesome"], ["I", "love", "Python"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)
word_vector = model.wv['love']
print(word_vector)
"""
[-8.6196875e-03 3.6657380e-03 5.1898835e-03 5.7419371e-03
7.4669169e-03 -6.1676763e-03 1.1056137e-03 6.0472824e-03
-2.8400517e-03 -6.1735227e-03 -4.1022300e-04 -8.3689503e-03
-5.6000138e-03 7.1045374e-03 3.3525396e-03 7.2256685e-03
6.8002464e-03 7.5307419e-03 -3.7891555e-03 -5.6180713e-04
2.3483753e-03 -4.5190332e-03 8.3887316e-03 -9.8581649e-03
6.7646410e-03 2.9144168e-03 -4.9328329e-03 4.3981862e-03
-1.7395759e-03 6.7113829e-03 9.9648498e-03 -4.3624449e-03
-5.9933902e-04 -5.6956387e-03 3.8508223e-03 2.7866268e-03
6.8910765e-03 6.1010956e-03 9.5384959e-03 9.2734173e-03
7.8980681e-03 -6.9895051e-03 -9.1558648e-03 -3.5575390e-04
-3.0998420e-03 7.8943158e-03 5.9385728e-03 -1.5456629e-03
1.5109634e-03 1.7900396e-03 7.8175711e-03 -9.5101884e-03
-2.0553112e-04 3.4691954e-03 -9.3897345e-04 8.3817719e-03
9.0107825e-03 6.5365052e-03 -7.1162224e-04 7.7104042e-03
-8.5343365e-03 3.2071066e-03 -4.6379971e-03 -5.0889566e-03
3.5896183e-03 5.3703380e-03 7.7695129e-03 -5.7665063e-03
7.4333595e-03 6.6254949e-03 -3.7098003e-03 -8.7456414e-03
5.4374672e-03 6.5097548e-03 -7.8755140e-04 -6.7098569e-03
-7.0859264e-03 -2.4970602e-03 5.1432536e-03 -3.6652375e-03
-9.3700597e-03 3.8267397e-03 4.8844791e-03 -6.4285635e-03
1.2085581e-03 -2.0748782e-03 2.4402141e-05 -9.8835090e-03
2.6920033e-03 -4.7501065e-03 1.0876465e-03 -1.5762257e-03
2.1966719e-03 -7.8815771e-03 -2.7171851e-03 2.6631975e-03
5.3466819e-03 -2.3915148e-03 -9.5100952e-03 4.5058774e-03]
"""
範例:
import pandas as pd
data = {'age': [22, 25, 47, 51, 62, 70, 80]}
df = pd.DataFrame(data)
bins = [0, 18, 35, 50, 65, 100]
labels = ['child', 'youth', 'adult', 'middle_aged', 'senior']
df['age_group'] = pd.cut(df['age'], bins=bins, labels=labels)
print(df)
"""
age age_group
0 22 youth
1 25 youth
2 47 adult
3 51 middle_aged
4 62 middle_aged
5 70 senior
6 80 senior
"""
特徵選擇是指從原始數據中選擇最具有信息量的特徵,以用於訓練機器學習模型。選擇更好的特徵可以提高模型的準確性,同時降低過擬合和計算成本。以下是一些常見的特徵選擇方法。
範例: 使用卡方檢驗篩選分類問題中的重要特徵
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.datasets import load_iris
import pandas as pd
# 載入數據集
data = load_iris()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.Series(data.target)
# 使用卡方檢驗進行特徵選擇
selector = SelectKBest(score_func=chi2, k=2)
X_new = selector.fit_transform(X, y)
print("原始特徵:", X.columns)
print("選擇的特徵:", X.columns[selector.get_support()])
應用情境: 在分類問題中,如用戶分類、垃圾郵件檢測等,根據卡方檢驗選擇與類別標籤高度相關的特徵。
範例: 使用遞歸特徵消除 (RFE) 進行特徵選擇
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
# 載入數據集
data = load_iris()
X = data.data
y = data.target
# 使用 RFE 進行特徵選擇
model = LogisticRegression(max_iter=1000)
rfe = RFE(estimator=model, n_features_to_select=2)
rfe = rfe.fit(X, y)
print("選擇的特徵:", rfe.support_)
print("特徵排名:", rfe.ranking_)
應用情境: 適用於特徵數量較少的情況,例如醫療數據分析中,通過 RFE 方法選擇對診斷結果影響最大的特徵。
範例: 使用 Lasso 回歸進行特徵選擇
from sklearn.linear_model import Lasso
from sklearn.datasets import load_boston
import numpy as np
# 載入數據集
data = load_boston()
X = data.data
y = data.target
# 使用 Lasso 進行特徵選擇
model = Lasso(alpha=0.1)
model.fit(X, y)
# 顯示每個特徵的係數
print("特徵係數:", model.coef_)
print("選擇的特徵:", np.where(model.coef_ != 0)[0])
應用情境: 在高維度數據集中,如房價預測中,通過 Lasso 回歸選擇出對預測結果影響最大的特徵。
範例: 使用 StabilitySelection 進行特徵選擇
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
import numpy as np
# 載入數據集
data = load_iris()
X = data.data
y = data.target
# 使用 LogisticRegression 進行特徵選擇
model = LogisticRegression(max_iter=1000)
selector = SelectFromModel(model)
selector.fit(X, y)
print("選擇的特徵:", np.array(data.feature_names)[selector.get_support()])
應用情境: 適合用於需要識別穩定且重要特徵的情況,如金融風險管理中選擇穩定影響信用評分的特徵。
範例: 使用 PCA 進行降維
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
# 載入數據集
data = load_digits()
X = data.data
y = data.target
# 使用 PCA 進行降維
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X)
# 繪製降維後的結果
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y)
plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.show()
應用情境: 適合處理高維數據的應用,如手寫數字識別中使用 PCA 降維以減少特徵數量,同時保留重要信息。
特徵提取 (Feature extraction) 是特徵工程中的一個重要步驟,旨在從原始數據中提取出有助於模型學習的特徵。這些提取的特徵通常比原始數據更具代表性,能夠更好地反映數據中的潛在模式。以下是一些常用的特徵提取方法。
範例:
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
# 加載Iris數據集
data = load_iris()
X = data.data
# PCA降維到2個主成分
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print("降維後的數據形狀:", X_pca.shape)
範例:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.datasets import load_iris
# 加載Iris數據集
data = load_iris()
X = data.data
y = data.target
# LDA降維到2個線性判別組
lda = LDA(n_components=2)
X_lda = lda.fit_transform(X, y)
print("降維後的數據形狀:", X_lda.shape)
範例:
from gensim.models import Word2Vec
# 示例句子
sentences = [['this', 'is', 'a', 'sentence'], ['this', 'is', 'another', 'sentence']]
# 訓練Word2Vec模型
model = Word2Vec(sentences, vector_size=50, min_count=1)
# 獲取詞嵌入
word_vector = model.wv['sentence']
print("詞語'sentence'的嵌入向量:", word_vector)
範例:
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
# 加載Digits數據集
data = load_digits()
X = data.data
# T-SNE降維到2維
tsne = TSNE(n_components=2)
X_tsne = tsne.fit_transform(X)
print("降維後的數據形狀:", X_tsne.shape)
範例:
from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
from tensorflow.keras.datasets import mnist
# 加載MNIST數據集
(X_train, _), (X_test, _) = mnist.load_data()
X_train = X_train.astype('float32') / 255.
X_test = X_test.astype('float32') / 255.
X_train = X_train.reshape((len(X_train), -1))
X_test = X_test.reshape((len(X_test), -1))
# 自動編碼器模型
input_dim = X_train.shape[1]
encoding_dim = 32
input_layer = Input(shape=(input_dim,))
encoded = Dense(encoding_dim, activation='relu')(input_layer)
decoded = Dense(input_dim, activation='sigmoid')(encoded)
autoencoder = Model(input_layer, decoded)
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
# 訓練模型
autoencoder.fit(X_train, X_train, epochs=50, batch_size=256, shuffle=True, validation_data=(X_test, X_test))
# 使用編碼部分提取特徵
encoder = Model(input_layer, encoded)
X_train_encoded = encoder.predict(X_train)
print("編碼後的數據形狀:", X_train_encoded.shape)