0
雷鋒網(wǎng) AI 科技評(píng)論按:本文是由來(lái)自英特爾的數(shù)據(jù)科學(xué)家 Dipanjan Sarkar 在 Medium 上發(fā)布的「特征工程」博客下篇,給領(lǐng)域內(nèi)的研究人員補(bǔ)充特征工程的相關(guān)知識(shí),不論學(xué)術(shù)研究、數(shù)據(jù)競(jìng)賽還是解決商業(yè)問(wèn)題都必不可少。在上篇中,作者介紹了連續(xù)型數(shù)值數(shù)據(jù)的特征工程處理方法。本篇為下篇,主要介紹離散數(shù)據(jù)的除了方法。雷鋒網(wǎng) AI 科技評(píng)論對(duì)原文進(jìn)行了編譯。
在上一篇文章中,我們介紹了許多用于處理結(jié)構(gòu)化的連續(xù)數(shù)值數(shù)據(jù)(continuous numeric data)的特征工程。而在本篇文章中,我們將繼續(xù)介紹另一種結(jié)構(gòu)化數(shù)據(jù)的處理 —— 這種數(shù)據(jù)本質(zhì)上是離散的,俗稱分類數(shù)據(jù)(categorical data)。由于在處理數(shù)值數(shù)據(jù)的時(shí)候,我們不必處理屬于某一分類類型的數(shù)據(jù)屬性中與每個(gè)類別值有關(guān)的額外的語(yǔ)義復(fù)雜性,因此處理數(shù)值數(shù)據(jù)通常比處理分類數(shù)據(jù)來(lái)得更加容易。我們將結(jié)合實(shí)際操作來(lái)討論處理分類數(shù)據(jù)的幾種編碼方案,以及處理大規(guī)模特征爆炸(通常稱為「維度詛咒 curse of dimensionality」)的一些流行技巧。
我認(rèn)為你現(xiàn)在必須要認(rèn)識(shí)到特征工程的動(dòng)機(jī)和重要性,我們?cè)谠撓盗形恼碌牡谝徊糠种幸苍敿?xì)強(qiáng)調(diào)了這一點(diǎn)。如果有必要,請(qǐng)快速溫習(xí)一下。簡(jiǎn)而言之,機(jī)器學(xué)習(xí)算法不能直接處理分類數(shù)據(jù),因此在開始為數(shù)據(jù)建模之前我們需要對(duì)數(shù)據(jù)進(jìn)行一些工程處理和轉(zhuǎn)換。
在深入研究特征工程之前,讓我們先了解一下分類數(shù)據(jù)。通常,在自然界中可分類的任意數(shù)據(jù)屬性都是離散值,這意味著它們屬于某一特定的有限類別。在模型預(yù)測(cè)的屬性或者變量(通常被稱為響應(yīng)變量 response variables)中,這些也經(jīng)常被稱為類別或者標(biāo)簽。這些離散值在自然界中可以是文本或者數(shù)字(甚至是諸如圖像這樣的非結(jié)構(gòu)化數(shù)據(jù))。分類數(shù)據(jù)有兩大類——定類(Nominal)和定序(Ordinal)。
在任意定類分類數(shù)據(jù)屬性中,這些屬性值之間沒有順序的概念。如下圖所示,舉個(gè)簡(jiǎn)單的例子,天氣分類。我們可以看到,在這個(gè)特定的場(chǎng)景中,主要有六個(gè)大類,而這些類之間沒有任何順序上的關(guān)系(刮風(fēng)天并不總是發(fā)生在晴天之前,并且也不能說(shuō)比晴天來(lái)的更小或者更大)
將天氣作為分類屬性
與天氣相類似的屬性還有很多,比如電影、音樂、電子游戲、國(guó)家、食物和美食類型等等,這些都屬于定類分類屬性。
定序分類的屬性值則存在著一定的順序意義或概念。例如,下圖中的字母標(biāo)識(shí)了襯衫的大小。顯而易見的是,當(dāng)我們考慮襯衫的時(shí)候,它的“大小”屬性是很重要的(S 碼比 M 碼來(lái)的小,而 M 碼又小于 L 碼等等)。
襯衫大小作為定序分類屬性
鞋號(hào)、受教育水平和公司職位則是定序分類屬性的一些其它例子。既然已經(jīng)對(duì)分類數(shù)據(jù)有了一個(gè)大致的理解之后,接下來(lái)我們來(lái)看看一些特征工程的策略。
在接受像文本標(biāo)簽這樣復(fù)雜的分類數(shù)據(jù)類型問(wèn)題上,各種機(jī)器學(xué)習(xí)框架均已取得了許多的進(jìn)步。通常,特征工程中的任意標(biāo)準(zhǔn)工作流都涉及將這些分類值轉(zhuǎn)換為數(shù)值標(biāo)簽的某種形式,然后對(duì)這些值應(yīng)用一些編碼方案。我們將在開始之前導(dǎo)入必要的工具包。
import pandas as pd
import numpy as np
定類屬性由離散的分類值組成,它們沒有先后順序概念。這里的思想是將這些屬性轉(zhuǎn)換成更具代表性的數(shù)值格式,這樣可以很容易被下游的代碼和流水線所理解。我們來(lái)看一個(gè)關(guān)于視頻游戲銷售的新數(shù)據(jù)集。這個(gè)數(shù)據(jù)集也可以在 Kaggle 和我的 GitHub 倉(cāng)庫(kù)中找到。
vg_df = pd.read_csv('datasets/vgsales.csv', encoding='utf-8')
vg_df[['Name', 'Platform', 'Year', 'Genre', 'Publisher']].iloc[1:7]
游戲銷售數(shù)據(jù)
讓我們首先專注于上面數(shù)據(jù)框中“視頻游戲風(fēng)格(Genre)”屬性。顯而易見的是,這是一個(gè)類似于“發(fā)行商(Publisher)”和“平臺(tái)(Platform)”屬性一樣的定類分類屬性。我們可以很容易得到一個(gè)獨(dú)特的視頻游戲風(fēng)格列表,如下。
genres = np.unique(vg_df['Genre'])
genres
Output
------
array(['Action', 'Adventure', 'Fighting', 'Misc', 'Platform', 'Puzzle', 'Racing', 'Role-Playing', 'Shooter', 'Simulation', 'Sports', 'Strategy'], dtype=object)
輸出結(jié)果表明,我們有 12 種不同的視頻游戲風(fēng)格。我們現(xiàn)在可以生成一個(gè)標(biāo)簽編碼方法,即利用 scikit-learn 將每個(gè)類別映射到一個(gè)數(shù)值。
from sklearn.preprocessing import LabelEncoder
gle = LabelEncoder()
genre_labels = gle.fit_transform(vg_df['Genre'])
genre_mappings = {index: label for index, label in enumerate(gle.classes_)}
genre_mappings
Output
------
{0: 'Action', 1: 'Adventure', 2: 'Fighting', 3: 'Misc', 4: 'Platform', 5: 'Puzzle', 6: 'Racing', 7: 'Role-Playing', 8: 'Shooter', 9: 'Simulation', 10: 'Sports', 11: 'Strategy'}
因此,在 LabelEncoder 類的實(shí)例對(duì)象 gle 的幫助下生成了一個(gè)映射方案,成功地將每個(gè)風(fēng)格屬性映射到一個(gè)數(shù)值。轉(zhuǎn)換后的標(biāo)簽存儲(chǔ)在 genre_labels 中,該變量允許我們將其寫回?cái)?shù)據(jù)表中。
vg_df['GenreLabel'] = genre_labels
vg_df[['Name', 'Platform', 'Year', 'Genre', 'GenreLabel']].iloc[1:7]
視頻游戲風(fēng)格及其編碼標(biāo)簽
如果你打算將它們用作預(yù)測(cè)的響應(yīng)變量,那么這些標(biāo)簽通??梢灾苯佑糜谥T如 sikit-learn 這樣的框架。但是如前所述,我們還需要額外的編碼步驟才能將它們用作特征。
定序?qū)傩允且环N帶有先后順序概念的分類屬性。這里我將以本系列文章第一部分所使用的神奇寶貝數(shù)據(jù)集進(jìn)行說(shuō)明。讓我們先專注于 「世代(Generation)」 屬性。
poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8')
poke_df = poke_df.sample(random_state=1, frac=1).reset_index(drop=True)
np.unique(poke_df['Generation'])
Output
------
array(['Gen 1', 'Gen 2', 'Gen 3', 'Gen 4', 'Gen 5', 'Gen 6'], dtype=object)
根據(jù)上面的輸出,我們可以看到一共有 6 代,并且每個(gè)神奇寶貝通常屬于視頻游戲的特定世代(依據(jù)發(fā)布順序),而且電視系列也遵循了相似的時(shí)間線。這個(gè)屬性通常是定序的(需要相關(guān)的領(lǐng)域知識(shí)才能理解),因?yàn)閷儆诘谝淮拇蠖鄶?shù)神奇寶貝在第二代的視頻游戲或者電視節(jié)目中也會(huì)被更早地引入。神奇寶貝的粉絲們可以看下下圖,然后記住每一代中一些比較受歡迎的神奇寶貝(不同的粉絲可能有不同的看法)。
基于不同類型和世代選出的一些受歡迎的神奇寶貝
因此,它們之間存在著先后順序。一般來(lái)說(shuō),沒有通用的模塊或者函數(shù)可以根據(jù)這些順序自動(dòng)將這些特征轉(zhuǎn)換和映射到數(shù)值表示。因此,我們可以使用自定義的編碼\映射方案。
gen_ord_map = {'Gen 1': 1, 'Gen 2': 2, 'Gen 3': 3, 'Gen 4': 4, 'Gen 5': 5, 'Gen 6': 6}
poke_df['GenerationLabel'] = poke_df['Generation'].map(gen_ord_map)
poke_df[['Name', 'Generation', 'GenerationLabel']].iloc[4:10]
神奇寶貝世代編碼
從上面的代碼中可以看出,來(lái)自 pandas 庫(kù)的 map(...) 函數(shù)在轉(zhuǎn)換這種定序特征的時(shí)候非常有用。
如果你還記得我們之前提到過(guò)的內(nèi)容,通常對(duì)分類數(shù)據(jù)進(jìn)行特征工程就涉及到一個(gè)轉(zhuǎn)換過(guò)程,我們?cè)谇耙徊糠置枋隽艘粋€(gè)轉(zhuǎn)換過(guò)程,還有一個(gè)強(qiáng)制編碼過(guò)程,我們應(yīng)用特定的編碼方案為特定的每個(gè)類別創(chuàng)建虛擬變量或特征分類屬性。
你可能想知道,我們剛剛在上一節(jié)說(shuō)到將類別轉(zhuǎn)換為數(shù)字標(biāo)簽,為什么現(xiàn)在我們又需要這個(gè)?原因很簡(jiǎn)單??紤]到視頻游戲風(fēng)格,如果我們直接將 GenereLabel 作為屬性特征提供給機(jī)器學(xué)習(xí)模型,則模型會(huì)認(rèn)為它是一個(gè)連續(xù)的數(shù)值特征,從而認(rèn)為值 10 (體育)要大于值 6 (賽車),然而事實(shí)上這種信息是毫無(wú)意義的,因?yàn)?em>體育類型顯然并不大于或者小于賽車類型,這些不同值或者類別無(wú)法直接進(jìn)行比較。因此我們需要另一套編碼方案層,它要能為每個(gè)屬性的所有不同類別中的每個(gè)唯一值或類別創(chuàng)建虛擬特征。
考慮到任意具有 m 個(gè)標(biāo)簽的分類屬性(變換之后)的數(shù)字表示,獨(dú)熱編碼方案將該屬性編碼或變換成 m 個(gè)二進(jìn)制特征向量(向量中的每一維的值只能為 0 或 1)。那么在這個(gè)分類特征中每個(gè)屬性值都被轉(zhuǎn)換成一個(gè) m 維的向量,其中只有某一維的值為 1。讓我們來(lái)看看神奇寶貝數(shù)據(jù)集的一個(gè)子集。
poke_df[['Name', 'Generation', 'Legendary']].iloc[4:10]
神奇寶貝數(shù)據(jù)集子集
這里關(guān)注的屬性是神奇寶貝的「世代(Generation)」和「?jìng)髌妫↙egendary)」?fàn)顟B(tài)。第一步是根據(jù)之前學(xué)到的將這些屬性轉(zhuǎn)換為數(shù)值表示。
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
# transform and map pokemon generations
gen_le = LabelEncoder()
gen_labels = gen_le.fit_transform(poke_df['Generation'])
poke_df['Gen_Label'] = gen_labels
# transform and map pokemon legendary status
leg_le = LabelEncoder()
leg_labels = leg_le.fit_transform(poke_df['Legendary'])
poke_df['Lgnd_Label'] = leg_labels
poke_df_sub = poke_df[['Name', 'Generation', 'Gen_Label', 'Legendary', 'Lgnd_Label']]
poke_df_sub.iloc[4:10]
轉(zhuǎn)換后的標(biāo)簽屬性
Gen_Label 和 Lgnd_Label 特征描述了我們分類特征的數(shù)值表示?,F(xiàn)在讓我們?cè)谶@些特征上應(yīng)用獨(dú)熱編碼方案。
# encode generation labels using one-hot encoding scheme
gen_ohe = OneHotEncoder()
gen_feature_arr = gen_ohe.fit_transform(poke_df[['Gen_Label']]).toarray()
gen_feature_labels = list(gen_le.classes_)
gen_features = pd.DataFrame(gen_feature_arr, columns=gen_feature_labels)
# encode legendary status labels using one-hot encoding scheme
leg_ohe = OneHotEncoder()
leg_feature_arr = leg_ohe.fit_transform(poke_df[['Lgnd_Label']]).toarray()
leg_feature_labels = ['Legendary_'+str(cls_label) for cls_label in leg_le.classes_]
leg_features = pd.DataFrame(leg_feature_arr, columns=leg_feature_labels)
通常來(lái)說(shuō),你可以使用 fit_transform 函數(shù)將兩個(gè)特征一起編碼(通過(guò)將兩個(gè)特征的二維數(shù)組一起傳遞給函數(shù),詳情查看文檔)。但是我們分開編碼每個(gè)特征,這樣可以更易于理解。除此之外,我們還可以創(chuàng)建單獨(dú)的數(shù)據(jù)表并相應(yīng)地標(biāo)記它們?,F(xiàn)在讓我們鏈接這些特征表(Feature frames)然后看看最終的結(jié)果。
poke_df_ohe = pd.concat([poke_df_sub, gen_features, leg_features], axis=1)
columns = sum([['Name', 'Generation', 'Gen_Label'], gen_feature_labels, ['Legendary', 'Lgnd_Label'], leg_feature_labels], [])
poke_df_ohe[columns].iloc[4:10]
神奇寶貝世代和傳奇狀態(tài)的獨(dú)熱編碼特征
此時(shí)可以看到已經(jīng)為「世代(Generation)」生成 6 個(gè)虛擬變量或者二進(jìn)制特征,并為「?jìng)髌妫↙egendary)」生成了 2 個(gè)特征。這些特征數(shù)量是這些屬性中不同類別的總數(shù)。某一類別的激活狀態(tài)通過(guò)將對(duì)應(yīng)的虛擬變量置 1 來(lái)表示,這從上面的數(shù)據(jù)表中可以非常明顯地體現(xiàn)出來(lái)。
考慮你在訓(xùn)練數(shù)據(jù)上建立了這個(gè)編碼方案,并建立了一些模型,現(xiàn)在你有了一些新的數(shù)據(jù),這些數(shù)據(jù)必須在預(yù)測(cè)之前進(jìn)行如下設(shè)計(jì)。
new_poke_df = pd.DataFrame([['PikaZoom', 'Gen 3', True], ['CharMyToast', 'Gen 4', False]], columns=['Name', 'Generation', 'Legendary'])
new_poke_df
新數(shù)據(jù)
你可以通過(guò)調(diào)用之前構(gòu)建的 LabelEncoder 和 OneHotEncoder 對(duì)象的 transform() 方法來(lái)處理新數(shù)據(jù)。請(qǐng)記得我們的工作流程,首先我們要做轉(zhuǎn)換。
new_gen_labels = gen_le.transform(new_poke_df['Generation'])
new_poke_df['Gen_Label'] = new_gen_labels
new_leg_labels = leg_le.transform(new_poke_df['Legendary'])
new_poke_df['Lgnd_Label'] = new_leg_labels
new_poke_df[['Name', 'Generation', 'Gen_Label', 'Legendary', 'Lgnd_Label']]
轉(zhuǎn)換之后的分類屬性
在得到了數(shù)值標(biāo)簽之后,接下來(lái)讓我們應(yīng)用編碼方案吧!
new_gen_feature_arr = gen_ohe.transform(new_poke_df[['Gen_Label']]).toarray()
new_gen_features = pd.DataFrame(new_gen_feature_arr, columns=gen_feature_labels)
new_leg_feature_arr = leg_ohe.transform(new_poke_df[['Lgnd_Label']]).toarray()
new_leg_features = pd.DataFrame(new_leg_feature_arr, columns=leg_feature_labels)
new_poke_ohe = pd.concat([new_poke_df, new_gen_features, new_leg_features], axis=1)
columns = sum([['Name', 'Generation', 'Gen_Label'], gen_feature_labels, ['Legendary', 'Lgnd_Label'], leg_feature_labels], [])
new_poke_ohe[columns]
獨(dú)熱編碼之后的分類屬性
因此,通過(guò)利用 scikit-learn 強(qiáng)大的 API,我們可以很容易將編碼方案應(yīng)用于新數(shù)據(jù)。
你也可以通過(guò)利用來(lái)自 pandas 的 to_dummies() 函數(shù)輕松應(yīng)用獨(dú)熱編碼方案。
gen_onehot_features = pd.get_dummies(poke_df['Generation'])
pd.concat([poke_df[['Name', 'Generation']], gen_onehot_features], axis=1).iloc[4:10]
使用 pandas 實(shí)現(xiàn)的獨(dú)熱編碼特征
上面的數(shù)據(jù)表描述了應(yīng)用在「世代(Generation)」屬性上的獨(dú)熱編碼方案,結(jié)果與之前的一致。
虛擬編碼方案(Dummy coding scheme)與獨(dú)熱編碼方案相似,不同之處在于,在虛擬編碼方案中,當(dāng)應(yīng)用于具有 m 個(gè)不同標(biāo)簽的分類特征時(shí),我們將得到 m-1 個(gè)二進(jìn)制特征。因此,分類變量的每個(gè)值都被轉(zhuǎn)換成 m-1 維向量。額外的特征將被完全忽略,因此如果分類取值范圍為{0, 1, ..., m-1},那么第一個(gè)(序號(hào)為 0)或者第 m 個(gè)(序號(hào)為 m-1)特征列將被丟棄,然后其對(duì)應(yīng)類別值由一個(gè) 0 向量表示。接下來(lái)我們嘗試通過(guò)丟棄第一個(gè)特征列(Gen 1)來(lái)將神奇寶貝“世代(Generation)”屬性轉(zhuǎn)換成虛擬編碼。
gen_dummy_features = pd.get_dummies(poke_df['Generation'], drop_first=True)
pd.concat([poke_df[['Name', 'Generation']], gen_dummy_features], axis=1).iloc[4:10]
神奇寶貝世代屬性的虛擬編碼
當(dāng)然你也可以通過(guò)如下操作來(lái)選擇丟棄最后一個(gè)特征列(Gen 6)。
gen_onehot_features = pd.get_dummies(poke_df['Generation'])
gen_dummy_features = gen_onehot_features.iloc[:,:-1]
pd.concat([poke_df[['Name', 'Generation']], gen_dummy_features], axis=1).iloc[4:10]
神奇寶貝世代屬性的虛擬編碼
基于上述描述,很明顯那些屬于被丟棄的類別(這里是 Gen 6)被表示為一個(gè)零向量。
效果編碼方案(Effect coding scheme)實(shí)際上非常類似于虛擬編碼方案,不同的是,對(duì)于在虛擬編碼方案中被編碼為零向量的類別,在效果編碼方案中將被編碼為全是 -1 的向量。下面的例子將清楚地展示這一點(diǎn)。
gen_onehot_features = pd.get_dummies(poke_df['Generation'])
gen_effect_features = gen_onehot_features.iloc[:,:-1]
gen_effect_features.loc[np.all(gen_effect_features == 0, axis=1)] = -1.
pd.concat([poke_df[['Name', 'Generation']], gen_effect_features], axis=1).iloc[4:10]
神奇寶貝世代的效果編碼特征
上面的輸出清楚地表明,與虛擬編碼中的零不同,屬于第六代的神奇寶貝現(xiàn)在由 -1 向量表示。
到目前為止,我們所討論的編碼方案在分類數(shù)據(jù)方面效果還不錯(cuò),但是當(dāng)任意特征的不同類別數(shù)量變得很大的時(shí)候,問(wèn)題開始出現(xiàn)。對(duì)于具有 m 個(gè)不同標(biāo)簽的任意分類特征這點(diǎn)非常重要,你將得到 m 個(gè)獨(dú)立的特征。這會(huì)很容易地增加特征集的大小,從而導(dǎo)致在時(shí)間、空間和內(nèi)存方面出現(xiàn)存儲(chǔ)問(wèn)題或者模型訓(xùn)練問(wèn)題。除此之外,我們還必須處理“維度詛咒”問(wèn)題,通常指的是擁有大量的特征,卻缺乏足夠的代表性樣本,然后模型的性能開始受到影響并導(dǎo)致過(guò)擬合。
因此,我們需要針對(duì)那些可能具有非常多種類別的特征(如 IP 地址),研究其它分類數(shù)據(jù)特征工程方案。區(qū)間計(jì)數(shù)方案是處理具有多個(gè)類別的分類變量的有效方案。在這個(gè)方案中,我們使用基于概率的統(tǒng)計(jì)信息和在建模過(guò)程中所要預(yù)測(cè)的實(shí)際目標(biāo)或者響應(yīng)值,而不是使用實(shí)際的標(biāo)簽值進(jìn)行編碼。一個(gè)簡(jiǎn)單的例子是,基于過(guò)去的 IP 地址歷史數(shù)據(jù)和 DDOS 攻擊中所使用的歷史數(shù)據(jù),我們可以為任一 IP 地址會(huì)被 DDOS 攻擊的可能性建立概率模型。使用這些信息,我們可以對(duì)輸入特征進(jìn)行編碼,該輸入特征描述了如果將來(lái)出現(xiàn)相同的 IP 地址,則引起 DDOS 攻擊的概率值是多少。這個(gè)方案需要?dú)v史數(shù)據(jù)作為先決條件,并且要求數(shù)據(jù)非常詳盡。
特征哈希方案(Feature Hashing Scheme)是處理大規(guī)模分類特征的另一個(gè)有用的特征工程方案。在該方案中,哈希函數(shù)通常與預(yù)設(shè)的編碼特征的數(shù)量(作為預(yù)定義長(zhǎng)度向量)一起使用,使得特征的哈希值被用作這個(gè)預(yù)定義向量中的索引,并且值也要做相應(yīng)的更新。由于哈希函數(shù)將大量的值映射到一個(gè)小的有限集合中,因此多個(gè)不同值可能會(huì)創(chuàng)建相同的哈希,這一現(xiàn)象稱為沖突。典型地,使用帶符號(hào)的哈希函數(shù),使得從哈希獲得的值的符號(hào)被用作那些在適當(dāng)?shù)乃饕幋鎯?chǔ)在最終特征向量中的值的符號(hào)。這樣能夠確保實(shí)現(xiàn)較少的沖突和由于沖突導(dǎo)致的誤差累積。
哈希方案適用于字符串、數(shù)字和其它結(jié)構(gòu)(如向量)。你可以將哈希輸出看作一個(gè)有限的 b bins 集合,以便于當(dāng)將哈希函數(shù)應(yīng)用于相同的值\類別時(shí),哈希函數(shù)能根據(jù)哈希值將其分配到 b bins 中的同一個(gè) bin(或者 bins 的子集)。我們可以預(yù)先定義 b 的值,它成為我們使用特征哈希方案編碼的每個(gè)分類屬性的編碼特征向量的最終尺寸。
因此,即使我們有一個(gè)特征擁有超過(guò) 1000 個(gè)不同的類別,我們?cè)O(shè)置 b = 10 作為最終的特征向量長(zhǎng)度,那么最終輸出的特征將只有 10 個(gè)特征。而采用獨(dú)熱編碼方案則有 1000 個(gè)二進(jìn)制特征。我們來(lái)考慮下視頻游戲數(shù)據(jù)集中的「風(fēng)格(Genre)」屬性。
unique_genres = np.unique(vg_df[['Genre']])
print("Total game genres:", len(unique_genres))
print(unique_genres)
Output
------
Total game genres: 12
['Action' 'Adventure' 'Fighting' 'Misc' 'Platform' 'Puzzle' 'Racing' 'Role-Playing' 'Shooter' 'Simulation' 'Sports' 'Strategy']
我們可以看到,總共有 12 中風(fēng)格的游戲。如果我們?cè)凇帮L(fēng)格”特征中采用獨(dú)熱編碼方案,則將得到 12 個(gè)二進(jìn)制特征。而這次,我們將通過(guò) scikit-learn 的 FeatureHasher 類來(lái)使用特征哈希方案,該類使用了一個(gè)有符號(hào)的 32 位版本的 Murmurhash3 哈希函數(shù)。在這種情況下,我們將預(yù)先定義最終的特征向量大小為 6。
from sklearn.feature_extraction import FeatureHasher
fh = FeatureHasher(n_features=6, input_type='string')
hashed_features = fh.fit_transform(vg_df['Genre'])
hashed_features = hashed_features.toarray()pd.concat([vg_df[['Name', 'Genre']], pd.DataFrame(hashed_features)], axis=1).iloc[1:7]
風(fēng)格屬性的特征哈希
基于上述輸出,「風(fēng)格(Genre)」屬性已經(jīng)使用哈希方案編碼成 6 個(gè)特征而不是 12 個(gè)。我們還可以看到,第 1 行和第 6 行表示相同風(fēng)格的游戲「平臺(tái)(Platform)」,而它們也被正確編碼成了相同的特征向量。
這些例子向你展示了一些在離散分類數(shù)據(jù)上進(jìn)行特征工程的主流策略。如果你看過(guò)了這篇文章的上篇,你將會(huì)發(fā)現(xiàn),比起處理連續(xù)數(shù)值數(shù)據(jù),處理分類數(shù)據(jù)要難得多,但是也很有趣!我們還談到了使用特征工程處理大型特征空間的一些方法,但是你應(yīng)該要記住還有其它技術(shù),包括特征選擇和降維方法來(lái)處理大型特征空間。我們將在未來(lái)的文章中介紹其中的一些方法。
PS: 文中所有代碼和數(shù)據(jù)集都可以從我的 GitHub 上獲得。
Via Understanding Feature Engineering (Part 2) — Categorical Data,雷鋒網(wǎng) AI 科技評(píng)論編譯
相關(guān)文章:
不會(huì)做特征工程的 AI 研究員不是好數(shù)據(jù)科學(xué)家!上篇 - 連續(xù)數(shù)據(jù)的處理方法
想成為真正的數(shù)據(jù)科學(xué)家,除了資歷你還需要這4個(gè)技能
Kaggle16000份問(wèn)卷揭示數(shù)據(jù)科學(xué)家平均畫像:30歲,碩士學(xué)位,年薪36萬(wàn)
數(shù)據(jù)科學(xué)家必須知道的 10 個(gè)深度學(xué)習(xí)架構(gòu)
雷峰網(wǎng)原創(chuàng)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。