Python 機器學習 基礎 之 數據表示與特征工程 【分類變量】的簡單說明
目錄
Python 機器學習 基礎 之 數據表示與特征工程 【分類變量】的簡單說明
一、簡單介紹
二、數據表示與特征工程
數據表示
特征工程
三、分類變量
1、One-Hot編碼(虛擬變量)
附錄
一、參考文獻
一、簡單介紹
Python是一種跨平臺的計算機程序設計語言。是一種面向對象的動態類型語言,最初被設計用于編寫自動化腳本(shell),隨著版本的不斷更新和語言新功能的添加,越多被用于獨立的、大型項目的開發。Python是一種解釋型腳本語言,可以應用于以下領域: Web 和 Internet開發、科學計算和統計、人工智能、教育、桌面界面開發、軟件開發、后端開發、網絡爬蟲。
Python 機器學習是利用 Python 編程語言中的各種工具和庫來實現機器學習算法和技術的過程。Python 是一種功能強大且易于學習和使用的編程語言,因此成為了機器學習領域的首選語言之一。Python 提供了豐富的機器學習庫,如Scikit-learn、TensorFlow、Keras、PyTorch等,這些庫包含了許多常用的機器學習算法和深度學習框架,使得開發者能夠快速實現、測試和部署各種機器學習模型。
Python 機器學習涵蓋了許多任務和技術,包括但不限于:
- 監督學習:包括分類、回歸等任務。
- 無監督學習:如聚類、降維等。
- 半監督學習:結合了有監督和無監督學習的技術。
- 強化學習:通過與環境的交互學習來優化決策策略。
- 深度學習:利用深度神經網絡進行學習和預測。
通過 Python 進行機器學習,開發者可以利用其豐富的工具和庫來處理數據、構建模型、評估模型性能,并將模型部署到實際應用中。Python 的易用性和龐大的社區支持使得機器學習在各個領域都得到了廣泛的應用和發展。
二、數據表示與特征工程
之前我們一直假設數據是由浮點數組成的二維數組,其中每一列是描述數據點的 連續特征 (continuous feature)。對于許多應用而言,數據的收集方式并不是這樣。一種特別常見的特征類型就是分類特征 (categorical feature),也叫離散特征 (discrete feature)。這種特征通常并不是數值。分類特征與連續特征之間的區別類似于分類和回歸之間的區別,只是前者在輸入端而不是輸出端。我們已經見過的連續特征的例子包括像素明暗程度和花的尺寸測量。分類特征的例子包括產品的品牌、產品的顏色或產品的銷售部門(圖書、服裝、硬件)。這些都是描述一件產品的屬性,但它們不以連續的方式變化。一件產品要么屬于服裝部門,要么屬于圖書部門。在圖書和服裝之間沒有中間部門,不同的分類之間也沒有順序(圖書不大于服裝也不小于服裝,硬件不在圖書和服裝之間,等等)。
無論你的數據包含哪種類型的特征,數據表示方式都會對機器學習模型的性能產生巨大影響。我們在之前案例可以中看到,數據縮放非常重要。換句話說,如果你沒有縮放數據(比如,縮放到單位方差),那么你用厘米還是英寸表示測量數據的結果將會不同。我們在之前的案例中還看到,用額外的特征擴充 (augment)數據也很有幫助,比如添加特征的交互項(乘積)或更一般的多項式。
對于某個特定應用來說,如何找到最佳數據表示,這個問題被稱為特征工程 (feature engineering),它是數據科學家和機器學習從業者在嘗試解決現實世界問題時的主要任務之一。用正確的方式表示數據,對監督模型性能的影響比所選擇的精確參數還要大。
機器學習中的數據表示與特征工程是模型訓練的基礎環節。
- 數據表示指的是如何將原始數據轉換為機器學習算法可以處理的形式。
- 特征工程是指從原始數據中提取或創建特征,以便更好地進行模型訓練。
以下是關于數據表示和特征工程的詳細介紹:
數據表示
數值數據: 最常見的數據類型,可以直接用于大多數機器學習算法。包括整數和浮點數。
- 示例:年齡、收入、評分等。
分類數據: 包含離散的類別或標簽,需要轉換為數值形式才能用于算法中。
- 標簽編碼(Label Encoding): 將每個類別映射為一個整數。
- 獨熱編碼(One-Hot Encoding): 使用二進制向量表示每個類別。
- 示例:性別(男、女)、城市(紐約、倫敦、巴黎)等。
文本數據: 需要轉換為數值表示,常用方法有:
- 詞袋模型(Bag of Words): 統計每個單詞在文本中出現的次數。
- TF-IDF(Term Frequency-Inverse Document Frequency): 考慮單詞在文檔和整個語料庫中的頻率。
- 詞向量(Word Embeddings): 使用預訓練的詞向量模型(如Word2Vec、GloVe)將單詞表示為向量。
時間序列數據: 包含時間戳和相應的值。常用的方法有:
- 時間特征提取: 提取如年、月、日、小時、星期幾等特征。
- 滑動窗口: 創建時間窗口來捕捉短期趨勢。
- 差分處理: 計算當前值與前一個值的差異。
特征工程
特征選擇: 從原始數據中選擇對預測任務最有用的特征。
- 過濾法(Filter Method): 基于統計特性選擇特征,如方差選擇、互信息選擇等。
- 包裹法(Wrapper Method): 使用特定的機器學習模型選擇特征,如遞歸特征消除(RFE)。
- 嵌入法(Embedded Method): 在模型訓練過程中選擇特征,如LASSO回歸中的L1正則化。
特征提取: 從原始數據中提取新的特征。
- 主成分分析(PCA): 降維技術,通過線性變換將數據映射到新的特征空間。
- 獨立成分分析(ICA): 類似于PCA,但假設特征是獨立的。
- 特征聚合: 將多個特征組合在一起,如求和、平均等。
特征構造: 基于現有特征創建新的特征。
- 多項式特征: 創建特征的多項式組合,如𝑥12,𝑥1𝑥2x12?,x1?x2?等。
- 交互特征: 計算兩個或多個特征的交互作用,如乘積、比值等。
特征縮放: 將特征值縮放到相同范圍,以提高模型的穩定性和收斂速度。
- 標準化(Standardization): 將特征縮放到均值為0,方差為1的分布。
- 歸一化(Normalization): 將特征縮放到指定范圍(通常是0到1)。
三、分類變量
作為例子,我們將使用美國成年人收入的數據集,該數據集是從 1994 年的普查數據庫中導出的。adult
數據集的任務是預測一名工人的收入是高于 50 000 美元還是低于 50 000 美元。這個數據集的特征包括工人的年齡、雇用方式(獨立經營、私營企業員工、政府職員等)、教育水平、性別、每周工作時長、職業,等等。表 4-1 給出了該數據集中的前幾個條目。
age | workclass | education | gender | hours-per-week | occupation | income | |
0 | 39 | State-gov | Bachelors | Male | 40 | Adm-clerical | <=50K |
1 | 50 | Self-emp-not-inc | Bachelors | Male | 13 | Exec-managerial | <=50K |
2 | 38 | Private | HS-grad | Male | 40 | Handlers-cleaners | <=50K |
3 | 53 | Private | 11th | Male | 40 | Handlers-cleaners | <=50K |
4 | 28 | Private | Bachelors | Female | 40 | Prof-specialty | <=50K |
5 | 37 | Private | Masters | Female | 40 | Exec-managerial | <=50K |
6 | 49 | Private | 9th | Female | 16 | Other-service | <=50K |
7 | 52 | Self-emp-not-inc | HS-grad | Male | 45 | Exec-managerial | >50K |
8 | 31 | Private | Masters | Female | 50 | Prof-specialty | >50K |
9 | 42 | Private | Bachelors | Male | 40 | Exec-managerial | >50K |
10 | 37 | Private | Some-college | Male | 80 | Exec-managerial | >50K |
這個任務屬于分類任務,兩個類別是收入 <=50k
和 >50k
。也可以預測具體收入,那樣就變成了一個回歸任務。但那樣問題將變得更加困難,而理解 50K 的分界線本身也很有趣。
在這個數據集中,age
(年齡)和 hours-per-week
(每周工作時長)是連續特征,我們知道如何處理這種特征。但 workclass
(工作類型)、education
(教育程度)、gender
(性別)、occupation
(職業)都是分類特征。它們都來自一系列固定的可能取值(而不是一個范圍),表示的是定性屬性(而不是數量)。
首先,假設我們想要在這個數據上學習一個 Logistic 回歸分類器。我們在第 2 章學過,Logistic 回歸利用下列公式進行預測,預測值為 :
其中?w?[i?] 和?b?是從訓練集中學到的系數,x?[i?] 是輸入特征。當?x?[i?] 是數字時這個公式才有意義,但如果?x?[2] 是?"Masters"
?或?"Bachelors"
?的話,這個公式則沒有意義。顯然,在應用 Logistic 回歸時,我們需要換一種方式來表示數據。下一節將會說明我們如何解決這一問題。
1、One-Hot編碼(虛擬變量)
到目前為止,表示分類變量最常用的方法就是使用 one-hot 編碼 (one-hot-encoding)或 N 取一編碼 (one-out-of-N encoding),也叫虛擬變量 (dummy variable)。虛擬變量背后的思想是將一個分類變量替換為一個或多個新特征,新特征取值為 0 和 1。對于線性二分類(以及 scikit-learn
中其他所有模型)的公式而言,0 和 1 這兩個值是有意義的,我們可以像這樣對每個類別引入一個新特征,從而表示任意數量的類別。
比如說,workclass
特征的可能取值包括 "Government Employee"
、"Private Employee"
、"Self Employed"
和 "Self Employed Incorporated"
。為了編碼這 4 個可能的取值,我們創建了 4 個新特征,分別叫作 "Government Employee"
、"Private Employee"
、"Self Employed"
和 "Self Employed Incorporated"
。如果一個人的 workclass
取某個值,那么對應的特征取值為 1,其他特征均取值為 0。因此,對每個數據點來說,4 個新特征中只有一個的取值為 1。這就是它叫作 one-hot 編碼或 N 取一編碼的原因。
其原理如表 4-2 所示。利用 4 個新特征對一個特征進行編碼。在機器學習算法中使用此數據時,我們將會刪除原始的 workclass
特征,僅保留 0-1 特征。
workclass | Government Employee | Private Employee | Self Employed | Self Employed Incorporated |
Government Employee | 1 | 0 | 0 | 0 |
Private Employee | 0 | 1 | 0 | 0 |
Self Employed | 0 | 0 | 1 | 0 |
Self Employed Incorporated | 0 | 0 | 0 | 1 |
我們使用的 one-hot 編碼與統計學中使用的虛擬編碼(dummy encoding)非常相似,但并不完全相同。為簡單起見,我們將每個類別編碼為不同的二元特征。在統計學中,通常將具有?k?個可能取值的分類特征編碼為?k?- 1 個特征(都等于零表示最后一個可能取值)。這么做是為了簡化分析(更專業的說法是,這可以避免使數據矩陣秩虧)。
將數據轉換為分類變量的 one-hot 編碼有兩種方法:一種是使用 pandas
,一種是使用 scikit-learn
。在寫作本書時,使用 pandas
要稍微簡單一些,所以我們選擇這種方法。首先,我們使用 pandas
從逗號分隔值(CSV)文件中加載數據:
import pandas as pd
from IPython.display import display# 文件中沒有包含列名稱的表頭,因此我們傳入header=None
# 然后在"names"中顯式地提供列名稱
data = pd.read_csv("data/adult.data", header=None, index_col=False,names=['age', 'workclass', 'fnlwgt', 'education', 'education-num','marital-status', 'occupation', 'relationship', 'race', 'gender','capital-gain', 'capital-loss', 'hours-per-week', 'native-country','income'])
# 為了便于說明,我們只選了其中幾列
data = data[['age', 'workclass', 'education', 'gender', 'hours-per-week','occupation', 'income']]
# IPython.display可以在Jupyter notebook中輸出漂亮的格式
display(data.head())
運行結果見表 4-3
age | workclass | education | gender | hours-per-week | occupation | income | |
0 | 39 | State-gov | Bachelors | Male | 40 | Adm-clerical | <=50K |
1 | 50 | Self-emp-not-inc | Bachelors | Male | 13 | Exec-managerial | <=50K |
2 | 38 | Private | HS-grad | Male | 40 | Handlers-cleaners | <=50K |
3 | 53 | Private | 11th | Male | 40 | Handlers-cleaners | <=50K |
4 | 28 | Private | Bachelors | Female | 40 | Prof-specialty | <=50K |
1)檢查字符串編碼的分類數據
讀取完這樣的數據集之后,最好先檢查每一列是否包含有意義的分類數據。在處理人工(比如網站用戶)輸入的數據時,可能沒有固定的類別,拼寫和大小寫也存在差異,因此可能需要預處理。舉個例子,有人可能將性別填為“male”(男性),有人可能填為“man”(男人),而我們希望能用同一個類別來表示這兩種輸入。檢查列的內容有一個好方法,就是使用?pandas Series
?(Series
?是?DataFrame
?中單列對應的數據類型)的?value_counts
?函數,以顯示唯一值及其出現次數:
print(data.gender.value_counts())
gender Male 21790 Female 10771 Name: count, dtype: int64
可以看到,在這個數據集中性別剛好有兩個值:Male
和 Female
,這說明數據格式已經很好,可以用 one-hot 編碼來表示。在實際的應用中,你應該查看并檢查所有列的值。為簡潔起見,這里我們將跳過這一步。
用 pandas
編碼數據有一種非常簡單的方法,就是使用 get_dummies
函數。get_dummies
函數自動變換所有具有對象類型(比如字符串)的列或所有分類的列(這是 pandas
中的一個特殊概念,我們還沒有講到):
print("Original features:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("Features after get_dummies:\n", list(data_dummies.columns))
Original features:['age', 'workclass', 'education', 'gender', 'hours-per-week', 'occupation', 'income'] Features after get_dummies:['age', 'hours-per-week', 'workclass_ ?', 'workclass_ Federal-gov', 'workclass_ Local-gov', 'workclass_ Never-worked', 'workclass_ Private', 'workclass_ Self-emp-inc', 'workclass_ Self-emp-not-inc', 'workclass_ State-gov', 'workclass_ Without-pay', 'education_ 10th', 'education_ 11th', 'education_ 12th', 'education_ 1st-4th', 'education_ 5th-6th', 'education_ 7th-8th', 'education_ 9th', 'education_ Assoc-acdm', 'education_ Assoc-voc', 'education_ Bachelors', 'education_ Doctorate', 'education_ HS-grad', 'education_ Masters', 'education_ Preschool', 'education_ Prof-school', 'education_ Some-college', 'gender_ Female', 'gender_ Male', 'occupation_ ?', 'occupation_ Adm-clerical', 'occupation_ Armed-Forces', 'occupation_ Craft-repair', 'occupation_ Exec-managerial', 'occupation_ Farming-fishing', 'occupation_ Handlers-cleaners', 'occupation_ Machine-op-inspct', 'occupation_ Other-service', 'occupation_ Priv-house-serv', 'occupation_ Prof-specialty', 'occupation_ Protective-serv', 'occupation_ Sales', 'occupation_ Tech-support', 'occupation_ Transport-moving', 'income_ <=50K', 'income_ >50K']
你可以看到,連續特征 age
和 hours-per-week
沒有發生變化,而分類特征的每個可能取值都被擴展為一個新特征:
data_dummies.head()
運行結果如下表:
age | hours-per-week | workclass_ ? | workclass_ Federal-gov | workclass_ Local-gov | workclass_ Never-worked | workclass_ Private | workclass_ Self-emp-inc | workclass_ Self-emp-not-inc | workclass_ State-gov | ... | occupation_ Machine-op-inspct | occupation_ Other-service | occupation_ Priv-house-serv | occupation_ Prof-specialty | occupation_ Protective-serv | occupation_ Sales | occupation_ Tech-support | occupation_ Transport-moving | income_ <=50K | income_ >50K | |
0 | 39 | 40 | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | ... | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
1 | 50 | 13 | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | ... | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
2 | 38 | 40 | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | ... | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
3 | 53 | 40 | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | ... | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
4 | 28 | 40 | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | ... | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
5 rows×46 columns
下面我們可以使用?values
?屬性將?data_dummies
?數據框(DataFrame
?)轉換為 NumPy 數組,然后在其上訓練一個機器學習模型。在訓練模型之前,注意要把目標變量(現在被編碼為兩個?income
?列)從數據中分離出來。將輸出變量或輸出變量的一些導出屬性包含在特征表示中,這是構建監督機器學習模型時一個非常常見的錯誤。
注意:
pandas
?中的列索引包括范圍的結尾,因此?'age':'occupation_Transport-moving'
?中包括?occupation_Transport-moving
?。這與 NumPy 數組的切片不同,后者不包括范圍的結尾,例如?np.arange(11)[0:10]
?不包括索引編號為 10 的元素。
在這個例子中,我們僅提取包含特征的列,也就是從?age
?到?occupation_ Transport-moving
?的所有列。這一范圍包含所有特征,但不包含目標:
features = data_dummies.loc[:, 'age':'occupation_ Transport-moving']
# 提取NumPy數組
X = features.values
y = data_dummies['income_ >50K'].values
print("X.shape: {} y.shape: {}".format(X.shape, y.shape))
X.shape: (32561, 44) y.shape: (32561,)
現在數據的表示方式可以被?scikit-learn
?處理,我們可以像之前一樣繼續下一步:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print("Test score: {:.2f}".format(logreg.score(X_test, y_test)))
Test score: 0.81
在這個例子中,我們對同時包含訓練數據和測試數據的數據框調用?
get_dummies
?。這一點很重要,可以確保訓練集和測試集中分類變量的表示方式相同。假設我們的訓練集和測試集位于兩個不同的數據框中。如果?
workclass
?特征的?"Private Employee"
?取值沒有出現在測試集中,那么?pandas
?會認為這個特征只有 3 個可能的取值,因此只會創建 3 個新的虛擬特征。現在訓練集和測試集的特征個數不相同,我們就無法將在訓練集上學到的模型應用到測試集上。更糟糕的是,假設?workclass
?特征在訓練集中有?"Government Employee"
?和?"Private Employee"
?兩個值,而在測試集中有?"Self Employed"
?和?"Self Employed Incorporated"
?兩個值。在兩種情況下,pandas
?都會創建兩個新的虛擬特征,所以編碼后的數據框的特征個數相同。但在訓練集和測試集中的兩個虛擬特征含義完全不同。訓練集中表示?"Government Employee"
?的那一列在測試集中對應的是?"Self Employed"
?。如果我們在這個數據上構建機器學習模型,那么它的表現會很差,因為它認為每一列表示的是相同的內容(因為位置相同),而實際上表示的卻是非常不同的內容。要想解決這個問題,可以在同時包含訓練數據點和測試數據點的數據框上調用?
get_dummies
?,也可以確保調用?get_dummies
?后訓練集和測試集的列名稱相同,以保證它們具有相同的語義。
在 adult
數據集的例子中,分類變量被編碼為字符串。一方面,可能會有拼寫錯誤;但另一方面,它明確地將一個變量標記為分類變量。無論是為了便于存儲還是因為數據的收集方式,分類變量通常被編碼為整數。例如,假設adult
?數據集中的人口普查數據是利用問卷收集的,workclass
?的回答被記錄為 0(在第一個框打勾)、1(在第二個框打勾)、2(在第三個框打勾),等等。現在該列包含數字 0 到 8,而不是像?"Private"
?這樣的字符串。如果有人觀察表示數據集的表格,很難一眼看出這個變量應該被視為連續變量還是分類變量。但是,如果知道這些數字表示的是就業狀況,那么很明顯它們是不同的狀態,不應該用單個連續變量來建模。
分類特征通常用整數進行編碼。它們是數字并不意味著它們必須被視為連續特征。一個整數特征應該被視為連續的還是離散的(one-hot 編碼的),有時并不明確。如果在被編碼的語義之間沒有順序關系(比如?
workclass
?的例子),那么特征必須被視為離散特征。對于其他情況(比如五星評分),哪種編碼更好取決于具體的任務和數據,以及使用哪種機器學習算法。
pandas
?的?get_dummies
?函數將所有數字看作是連續的,不會為其創建虛擬變量。為了解決這個問題,你可以使用?scikit-learn
?的?OneHotEncoder
?,指定哪些變量是連續的、哪些變量是離散的,你也可以將數據框中的數值列轉換為字符串。為了說明這一點,我們創建一個兩列的?DataFrame
?對象,其中一列包含字符串,另一列包含整數:
# 創建一個DataFrame,包含一個整數特征和一個分類字符串特征
demo_df = pd.DataFrame({'Integer Feature': [0, 1, 2, 1],'Categorical Feature': ['socks', 'fox', 'socks', 'box']})
display(demo_df)
運行結果見表 4-4
Integer Feature | Categorical Feature | |
0 | 0 | socks |
1 | 1 | fox |
2 | 2 | socks |
3 | 1 | box |
使用?get_dummies
?只會編碼字符串特征,不會改變整數特征,正如表 4-5 所示。
pd.get_dummies(demo_df)
Integer Feature | Categorical Feature_box | Categorical Feature_fox | Categorical Feature_socks | |
0 | 0 | FALSE | FALSE | TRUE |
1 | 1 | FALSE | TRUE | FALSE |
2 | 2 | FALSE | FALSE | TRUE |
3 | 1 | TRUE | FALSE | FALSE |
如果你想為“Integer Feature”這一列創建虛擬變量,可以使用?columns
?參數顯式地給出想要編碼的列。于是兩個特征都會被當作分類特征處理(見表 4-6):
demo_df['Integer Feature'] = demo_df['Integer Feature'].astype(str)
pd.get_dummies(demo_df, columns=['Integer Feature', 'Categorical Feature'])
Integer Feature_0 | Integer Feature_1 | Integer Feature_2 | Categorical Feature_box | Categorical Feature_fox | Categorical Feature_socks | |
0 | TRUE | FALSE | FALSE | FALSE | FALSE | TRUE |
1 | FALSE | TRUE | FALSE | FALSE | TRUE | FALSE |
2 | FALSE | FALSE | TRUE | FALSE | FALSE | TRUE |
3 | FALSE | TRUE | FALSE | TRUE | FALSE | FALSE |
附錄
一、參考文獻
參考文獻:[德] Andreas C. Müller [美] Sarah Guido 《Python Machine Learning Basics Tutorial》