今天的內容是要敘述特徵工程的目的以及為什麼需要特徵工程。
此篇文是由 Joyce 所撰寫
特徵工程就是篩選特徵的工程,對原始的數據進行一系列的分析討論,從中選取重要的特徵,讓資料可以更好來呈現。通常會將特徵工程分為兩大類
為了篩選出重要的特徵,資料的前處理一定要做好,當資料有大多的雜訊,那麼不管再怎麼分析,再怎麼訓練,都不會有好的結果,甚至有可能結果其實是錯誤的,但是我們並不曉得,因此資料的前處理是一個重要的步驟。以下舉一些在資料前處理常見的作法。
當資料有缺失值的時候,通常有兩種做法,一種是刪除該資料,大部分的時候,我們不會採取刪除資料,但當缺失值太多的時候,像是超過一半的值都消失了,可以考慮將整個特徵刪掉。第二種是將資料填滿,在這裡我們就只考慮第二種。填滿的方式,有很多種方式,像是使用統計值(眾數、平均數)、依照前面的值、差值法,我們就不去仔細探討。
import pandas as pd # 引入pandas
df = pd.DataFrame({
"fruit": ["apple", "banana", "peach",
"watermelon", "straberry",
"guava", "cherry"],
"price": [50, 100, None, 70, 80, 60, None]
}) # 建立資料
df.fillna(50) # 填入固定值
df.fillna(df["price"].mean()) # 填入平均值
df.fillna(method='ffill') # 填補值依據前一個的值
有時資料會以字串的方式呈現,但對於分析來說這並不好處理,例如,再填入性別質料的時候,我們會輸入男和女,可是其實我們可以經過轉換,也可以代表同樣的意義,而在分析上會更方便,其他的例子像是月份、星期等,數值方式的呈現一定比字串來的好處理。
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"gender" : ["boy", "girl", "girl", "boy"],
"score" : [59, 100, 80, 49]
}) # 建立資料
# Using inplace to change the origin data
df["gender"].replace("boy", 0, inplace = True) # 將"boy"換成數值0
df["gender"].replace("girl", 1, inplace = True) # 將"girl"換成數值1
想要將兩個(以上)的特徵,一起比較,此時,不同單位造成很大的困擾,最好的方式就是要標準化處理,讓資料變成無單位,能一起分析。常見的標準化方式分為兩種,min-max標準化及Z-score標準化
min-max標準化
Z-score標準化
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"weight" : [80, 45, 60, 70],
"height" : [180, 160, 165, 170]
}) # 建立資料
max_weight = df["weight"].max() # 體重的最大值
max_height = df["height"].max() # 身高的最大值
min_weight = df["weight"].min() # 體重的最小值
min_height = df["height"].min() # 身高的最小值
df["min-max_weight"] = (df["weight"] - min_weight) / (max_weight-min_weight) # 體重的min-max
df["min-max_height"] = (df["height"] - min_height) / (max_height-min_height) # 身高的min-max
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"weight" : [80, 45, 60, 70],
"height" : [180, 160, 165, 170]
}) # 建立資料
mu_weight = df["weight"].mean() # 體重平均
mu_height = df["height"].mean() # 身高平均
std_weight = df["weight"].std() # 體重標準差
std_height = df["height"].std() # 身高標準差
df["z_score_weight"] = (df["weight"] - mu_weight) / std_weight # 體重的Z-score
df["z_score_height"] = (df["height"] - mu_height) / std_height # 身高的Z-score
他的涵義其實就是前面介紹過的"資料多餘",但在前面我們處理的方式是手動替換,而在sklearn的函數庫裡,有提供Label encoding的寫法,可以將字串轉換成數值,且不會在新增欄位。
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"gender" : ["boy", "girl", "girl", "boy"],
"score" : [59, 100, 80, 49]
}) # 建立資料
from sklearn.preprocessing import LabelEncoder # 引入LabelEncoder
labelencoder = LabelEncoder()
df["gender"] = labelencoder.fit_transform(df["gender"]) # 將"gender"裡的字串自動轉換成數值
One hot encoding通常會跟Label encoding一起比較,不同的點在於One hot encoding會增加新的欄位,一種資料就增加一欄,用1表示屬於該欄,0表示不屬於,因此當資料種類很多的時候就會新增很多欄,儲存的空間便很大,那麼也許你會想問那我幹嘛使用他,用Label encoding的方式就好啊,那是因為Label encoding的數值會有0、1、2、3...,如果分析不當,錯認它有排序關係的話,那麼結果就會大錯特錯。
注意一點,One hot encoding 是無法直接對字串進行處理的,因此常見的方式是先做Label encoding,再把其結果,換成One hot encoding,在sklearn裡,出來的結過會是array。
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"gender" : ["boy", "girl", "girl", "boy"],
"score" : [59, 100, 80, 49]
}) # 建立資料
from sklearn.preprocessing import LabelEncoder # 引入LabelEncoder
labelencoder = LabelEncoder()
df["gender"] = labelencoder.fit_transform(df["gender"]) # 將"gender"裡的字串自動轉換成數值
from sklearn.preprocessing import OneHotEncoder # 引入OneHotEncoder
onehotencoder = OneHotEncoder()
onehot_df = onehotencoder.fit_transform(df[["gender"]])
# Change the onehotencoder object to array and make it to be the DataFrame
onehot_df = pd.DataFrame(onehot_df.toarray(), columns=["gender_boy", "gender_girl"])
# Concat the onehotencoder DataFrame to the origin DataFrame
display(pd.concat([df, onehot_df], axis=1))
有沒有覺得小麻煩呢?要轉兩次,其實pandas也可以做One hot encoding,使用get_dummies(),如此一來就不用先將字串轉成數值了,而且出來的型態直接是DataFrame了,不用再做轉換。
df = pd.DataFrame({
"name" : ["Justin", "Joyce", "Alice", "John"],
"gender" : ["boy", "girl", "girl", "boy"],
"score" : [59, 100, 80, 49]
}) # 建立資料
gender_df = pd.get_dummies(df["gender"]) # 轉換成One hot encoding
特徵選取核心的概念就是從原始的資料中挑選出具有鑑別能力且有效的特徵,使其集合達到最佳化,讓原始的數據變得更加有價值。下面介紹三種常見的分析方式。
用統計的技巧獲得的分數來進行特徵的選擇,像是卡方檢定、皮爾松相關係數,F分數檢驗。我們在這裡介紹卡方檢定。
卡方檢定是用來比較兩變數的關聯性,例如討論身高與體重是否相互獨立,或是相依。在進行卡方檢定的第一步驟是要先有一個虛無假設($H_0$),經由卡方檢定的值再決定要不要拒絕虛無假設,
,藉此,可以過濾出比較重要的特徵。
特別注意,卡方檢定裏頭的值必須為正數,不能有負數。
前提假設:
• 所有的變項為類別變項
• 樣本須為獨立變項
我們現在用一個小例子來分析,數據如下面的表格,那麼性別會有來自城市還是鄉村有關嗎?我們假設$H_0$為性別的不同與來自城市鄉村無關。
城市 | 鄉村 | 總和 | |
---|---|---|---|
男生 | 60 | 40 | 100 |
女生 | 50 | 50 | 100 |
總和 | 110 | 90 | 200 |
from scipy.stats import chi2_contingency # 引入chi2_contingency
import numpy as np # 引入numpy
kf_data = np.array([[60,40], [50,50]])
kf = chi2_contingency(kf_data)
print(kf)
(1.6363636363636362, 0.20082512269514174, 1,
array([[55., 45.],
[55., 45.]]))
第一個值是卡方值,第二個值為p值,第三個值是自由度,第四個值為原始資料的理論值。
因為p值大於0.05,因此無法拒絕,接受$H_0$的假設,即互相獨立。
它的想法是跟演算法同時進行,在過程中選取特徵,因為跟演算法相關,所以十分依賴模型的選擇。因為在過程中要使用模型及要考慮所有特徵,所以花費的時間會比過濾法快,但獲得的結果會更有效。最常被拿來使用的模型是SelectFromModel,且在sklearn的函數庫有包含,要使用的化引入即可。
這個模型是一種通用的轉換器,只要带有conef_或者feature_importances的屬性,即可使用,如果conef_或feature_importances低於閾值,表示此特徵不重要。
包裝的意思就是根據目標函數,每次選擇幾個特徵,或者排除幾個特徵,作為不同的組合用模型進行特徵的挑選。良好的模型有決策樹、SVM、KNN等。常見的方式有:前向搜索、後向搜索。
前向搜索的意思是,剛開始的集合是空的,在每輪的選取都在加入一個還沒有選取過的特徵做分析,直到限制的數量停止,最後在從所有的可能中選取錯誤率最小的。
後向搜索的意思則跟前向搜索的意思相反,在一開始的集合包含所有的元素,在新的一輪選取一個特徵去除,後來就跟前向搜索一樣,直到限制的數量停止,最後在從所有的可能中選取錯誤率最小的。
特徵工程的範圍非常的廣,這裡只是介紹一小部分,主要在強調概念的部分,其背後的數學原理我們就先不討論。希望大家透過這篇文章對特徵工程有初步的了解。