目前,很多機器學習專案的模型選擇開始轉向自動化,而特徵工程仍然主要以人工為主。這個過程的重要性可能比模型選擇更重要,人工得到的特徵總帶有一定的侷限性。在本文中作者將為我們介紹如何使用 Feature Tools Python 庫實現特徵工程自動化,專案已開源。
機器學習越來越多地從人工設計模型轉向使用 H20、TPOT 和 auto-sklearn 等工具自動優化的工具。這些庫以及隨機搜尋(參見《Random Search for Hyper-Parameter Optimization》)等方法旨在通過尋找匹配資料集的最優模型來簡化模型選擇和機器學習調優過程,而幾乎不需要任何人工干預。然而,特徵工程作為機器學習流程中可能最有價值的一個方面,幾乎完全是人工的。
特徵工程也被稱為特徵構造,是從現有資料中構造新的特徵從而訓練機器學習模型的過程。這一步可能比實際上使用的模型更重要,因為一個機器學習演算法只能從我們給定的資料中學習,所以構造一個和任務相關的特徵是至關重要的,參見優質論文《A Few Useful Things to Know about Machine Learning》。
通常,特徵工程是一個冗長的人工過程,依賴於領域知識、直覺和資料操作。這個過程可能是極其枯燥的,同時最終得到的特徵將會受到人的主觀性和時間的限制。特徵工程自動化旨在通過從資料集中自動構造候選特徵,並從中選擇最優特徵用於訓練來幫助資料科學家。
在本文中,我們將介紹一個使用 Feature Tools Python 庫實現特徵工程自動化的例子。我們將使用一個示例資料集來說明基本概念(繼續關注之後使用真實世界資料的例子)。本文完整程式碼可在 Github 上找到。
特徵工程基本概念
特徵工程意味著從現有的資料中構造額外特徵,這些特徵通常分佈在多張相關的表中。特徵工程需要從資料中提取相關資訊並將其存入單張表格中,然後被用來訓練機器學習模型。
構造特徵是一個非常耗時的過程,因為每個新的特徵通常需要幾步才能構造,特別是當使用多張表的資訊時。我們可以將特徵構造的操作分為兩類:「轉換」和「聚合」。以下通過幾個例子來看看這些概念的實際應用。
通過從一或多列中構造新的特徵,「轉換」作用於單張表(在 Python 中,表是一個 Pandas DataFrame)。舉個例子,若有如下的客戶表:
我們可以通過查詢 joined 列中的月份或是自然對數化 income 列的資料來構造新的特徵。這些都是轉換操作,因為它們只用到了一張表的資訊。
另一方面,「聚合」是跨表實現的,並使用一對多的關聯來對觀測值分組,然後計算統計量。例如,若我們有另外一張包含客戶貸款資訊的表格,其中每個客戶可能有多項貸款,我們便可以計算每個客戶貸款的平均值、最大值和最小值等統計量。
這個過程包括根據不同客戶對貸款表進行分組並計算聚合後的統計量,然後將結果整合到客戶資料中。以下是我們在 Python 中使用 Pandas 庫執行此操作。
import pandas as pd
# Group loans by client id and calculate mean, max, min of loans
stats = loans.groupby('client_id')['loan_amount'].agg(['mean', 'max', 'min'])
stats.columns = ['mean_loan_amount', 'max_loan_amount', 'min_loan_amount']
# Merge with the clients dataframe
stats = clients.merge(stats, left_on = 'client_id', right_index=True, how = 'left')
stats.head(10)
這些操作本身並不困難,但是如果有數百個變數分佈在數十張表中,這個過程將無法通過人工完成。理想情況下,我們希望有一個解決方案能夠在不同表間自動執行轉換和聚合操作,並將結果整合到一張表中。儘管 Pandas 是一個很好的資源,但是仍然有許多資料操作需要我們人工完成!有關人工特徵工程的更多資訊,請查閱《Python Data Science Handbook》。
特徵工具
幸運的是,Feature Tools 正是我們正在找尋的解決方案。這個開源的 Python 庫可以從一組相關的表中自動構造特徵。特徵工具基於名為「深度特徵合成」的方法(參見《Deep Feature Synthesis: Towards Automating Data Science Endeavors》),這個方法的名字聽起來比其本身更高大上(這個名字源於疊加了多重特徵,而不是因為使用了深度學習方法!)。
深度特徵合成疊加多個轉換和聚合操作,這在特徵工具的詞庫中被稱為特徵基元,以便通過分佈在多張表內的資料來構造新的特徵。與機器學習中的大多數方法一樣,這是建立在簡單概念基礎之上的複雜方法。通過一次學習一個構造塊,我們可以很好地理解這個強大的方法。
首先,讓我們看一下示例資料。我們已經看到了上面的一些資料集,並且完整的表組如下所示:
clients: 關於信用社客戶的基本資訊。每個客戶只對應資料框中的一行。
loans: 向使用者提供的貸款。每項貸款只對應資料框中的一行,但是客戶可能有多項貸款。
payments:貸款還本的支付。每筆支付只對應一行,但是每項貸款可以有多筆支付。
如果我們有一個機器學習任務,例如預測客戶未來是否會償還一項貸款,我們希望將所有關於客戶的資訊整合到一張表中。這些表是相關的(通過 client_id 和 loan_id 變數),並且我們可以通過一系列轉換和聚合操作來人工實現這個過程。然而,我們很快就可以使用特徵工具來自動實現這個過程。
實體和實體集
特徵工具的前兩個概念的是「實體」和「實體集」。一個實體就是一張表(或是 Pandas 中的一個 DataFrame(資料框))。一個實體集是一組表以及它們之間的關聯。將一個實體集看成另一種 Python 資料結構,並帶有自己的方法和屬性。
我們可以通過以下操作在特徵工具中建立一個空的實體集:
import featuretools as ft
# Create new entityset
es = ft.EntitySet(id = 'clients')
現在我們需要整合兩個實體。每個實體都必須帶有一個索引,它是一個包含所有唯一元素的列。就是說,索引中的每個值只能在表中出現一次。在 clients 資料框中的索引是 client_id,因為每個客戶在該資料框中只對應一行。我們使用以下語法將一個帶有索引的實體新增一個實體集中:
# Create an entity from the client dataframe
# This dataframe already has an index and a time index
es = es.entity_from_dataframe(entity_id = 'clients', dataframe = clients,
index = 'client_id', time_index = 'joined')
loans 資料框還有另外一個唯一的索引,loan_id,同時將其新增到實體集的語法與 clients 一樣。然而,payments 資料框不存在唯一索引。當我們把 payments 資料框新增到實體集中時,我們需要傳入引數 make_index = True,同時指定索引的名字。另外,儘管特徵工具能自動推斷實體中每列的資料型別,但是我們可以通過將列資料型別的字典傳遞給引數 variable_types 來覆蓋它。
# Create an entity from the payments dataframe
# This does not yet have a unique index
es = es.entity_from_dataframe(entity_id = 'payments',
dataframe = payments,
variable_types = {'missed': ft.variable_types.Categorical},
make_index = True,
index = 'payment_id',
time_index = 'payment_date')
對於此資料框,儘管 missed 是一個整數,但是它不是一個數值變數,因為它只能取 2 個離散的數值,所以在特徵工具中,將其看成一個分類變數。在將該資料框新增到實體集中後,我們檢查整個實體集:
列的資料型別已根據我們指定的修正方案被正確推斷出來。接下來,我們需要指定實體集中表是如何關聯的。
表的關聯
考慮兩張表之間「關聯」的最好方法是類比父子之間的關聯。這是一種一對多的關聯:每個父親可以有多個兒子。對錶來說,每個父親對應一張父表中的一行,但是子表中可能有多行對應於同一張父表中的多個兒子。
例如,在我們的資料集中,clients 資料框是 loans 資料框的一張父表。每個客戶只對應 clients 表中的一行,但是可能對應 loans 表中的多行。同樣,loans 表是 payments 表的一張父表,因為每項貸款可以有多項支付。父親通過共享變數與兒子相關聯。當我們執行聚合操作的時候,我們根據父變數對子表進行分組,並計算每個父親的兒子的統計量。
為了形式化特徵工具中的關聯規則,我們僅需指定連線兩張表的變數。clients 表和 loans 表通過 client_id 變數連線,同時 loans 表和 payments 表通過 loan_id 變數連線。建立關聯並將其新增到實體集中的語法如下所示:
# Relationship between clients and previous loans
r_client_previous = ft.Relationship(es['clients']['client_id'],
es['loans']['client_id'])
# Add the relationship to the entity set
es = es.add_relationship(r_client_previous)
# Relationship between previous loans and previous payments
r_payments = ft.Relationship(es['loans']['loan_id'],
es['payments']['loan_id'])
# Add the relationship to the entity set
es = es.add_relationship(r_payments)
es
該實體集現在包含三個實體(表),以及將這些表連線在一起的關聯規則。在新增實體和形式化關聯規則之後,實體集就完整了並準備好從中構造新的特徵。
特徵基元
在我們深入瞭解深度特徵合成之前,我們需要了解特徵基元的概念。我們其實早就知道是什麼了,只是我們剛剛用不同的名字來稱呼它們!它們只是我們用來構造新特徵的操作:
聚合:根據父與子(一對多)的關聯完成的操作,也就是根據父親分組並計算兒子的統計量。一個例子就是根據 client_id 對 loan 表分組並找到每個客戶的最大貸款額。
轉換:對一張表中一或多列完成的操作。一個例子就是取一張表中兩列之間的差值或者取一列的絕對值。
在特徵工具中單獨使用這些基元或者疊加使用這些基元可以構造新的特徵。以下是特徵工具中一些特徵基元的列表,也可以自定義特徵基元。
特徵基元
這些基元可以單獨使用或是組合使用以構造新的特徵。為了使用特定的基元構造新的特徵,我們使用 ft.dfs 函式(代表深度特徵合成)。我們傳入 entityset 和 target_entity,這是我們想要在其中新增特徵的表,被選引數 trans_primitives(轉換)和 agg_primitives(聚合)。
public class MyActivity extends AppCompatActivity {
@Override //override the function
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
OkhttpManager.getInstance().setTrustrCertificates(getAssets().open("mycer.cer");
OkHttpClient mOkhttpClient= OkhttpManager.getInstance().build();
} catch (IOException e) {
e.printStackTrace();
}
}
返回的是包含每個客戶新特徵的資料框(因為我們定義客戶為 target_entity)。比如我們有每個客戶加入的月份,這是一個轉換操作的特徵基元:
我們也有許多聚合操作的基元,比如每個客戶的平均支付總額:
儘管我們僅指定了一些特徵基元,但是特徵工具可以通過組合和疊加這些基元來構造新的特徵。
完整的資料框包含 793 列的新特徵!
深度特徵合成
我們現在具備理解深度特徵合成(dfs)的一切條件。事實上,我們已經在前面的函式呼叫中執行了 dfs!深度特徵只是疊加多個基元構造的一個特徵,而 dfs 只是構造這些特徵的過程的名稱。深度特徵的深度是構造這個特徵所需的基元數量。
例如,MEAN(payments.payment_amount)列是深度為 1 的特徵,因為它是使用單個聚合操作構造的。LAST(loans(MEAN(payments.payment_amount))是一個深度為 2 的特徵,它是由兩個疊加的聚合操作構造的:MEAN 列之上的 LAST(最近的)列。這表示每個客戶最近的貸款平均支付額。
我們可以疊加任意深度的特徵,但在實踐中,我從沒有使用超過 2 個深度的特徵。此外,這些特徵很難解釋,但是我鼓勵任何對「深入」感興趣的人。
我們不必人工指定特徵基元,但可以讓特徵工具自動為我們選取特徵。為此,我們使用相同的 ft.dfs 函式呼叫,但是不傳入任何特徵基元。
# Perform deep feature synthesis without specifying primitives
features, feature_names = ft.dfs(entityset=es, target_entity='clients',
max_depth = 2)
features.head()
特徵工具構造了很多特徵供我們使用。儘管這個過程確實能自動構造新的特徵,但是它不會取代資料科學家,因為我們仍然需要弄清楚如何處理這些特徵。例如,我們的目的是預測一位客戶是否會償還貸款,我們可以尋找與特定結果最相關的特徵。此外,如果我們具有領域知識,我們可以用這些知識來選擇指定的特徵基元或候選特徵的種子深度特徵合成。
下一步
特徵工程自動化解決了一個問題,但是帶來了另一個問題:特徵太多了。儘管在擬合一個模型之前很難說哪些特徵是重要的,但很可能不是所有這些特徵都與我們想要訓練的模型的任務相關。此外,擁有太多特徵(參見《Irrelevant Features and the Subset Selection Problem》)可能會導致模型效能不佳,因為較無益的特徵會淹沒那些更重要的特徵。
特徵過多問題以維度災難著稱。隨著特徵數量的上升(資料維度增長),模型越來越難以學習特徵與目標之間的對映關係。事實上,讓模型表現良好所需的資料量與特徵數量成指數關係。
維度災難與特徵降維(也叫特徵選擇,去除不相關特徵的過程)相對。這可以採用多種形式:主成分分析(PCA)、SelectKBest、使用模型中特徵的重要性或使用深度神經網路進行自編碼。但是,特徵降維是另一篇文章的不同主題。到目前為止,我們知道我們可以使用特徵工具以最小的努力從許多表中構造大量的特徵!
結論
與機器學習中的許多主題一樣,使用特徵工具進行特徵工程自動化是一個基於簡單想法的複雜概念。使用實體集、實體和關聯的概念,特徵工具可以執行深度特徵合成操作來構造新的特徵。深度特徵合成可以依次疊加特徵基元:「聚合」,它們在多張表間的一對多關聯中起作用,以及「轉換」,是應用於單張表中一或多列以從多張表中構造新的特徵的函式。
在之後的文章中,我將介紹如何在現實世界的問題上使用這項技術,即在 Kaggle 上舉辦的房屋信用違約風險競賽(https://www.kaggle.com/c/home-credit-default-risk)。請繼續關注那則帖子,與此同時,閱讀這則說明以開始競賽(https://towardsdatascience.com/machine-learning-kaggle-competition-part-one-getting-started-32fb9ff47426)! 我希望你們可以使用特徵工程自動化作為資料科學工作中的輔助工具。我們的模型與我們提供的資料一樣好,並且特徵工程自動化可以使特徵構造的過程更高效。
有關特徵工具更多的資訊,包括高階用法,請檢視線上文件(https://docs.featuretools.com/)。要了解在實踐中如何使用特徵工具,請閱讀開源庫的開發公司 Feature Labs 的工作(https://www.featurelabs.com/)。
原文連結:https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219