特徵工程究竟是什麼呢?顧名思義,其本質是一項工程活動,目的是最大限度地從原始數據中提取特徵以供算法和模型使用。git
在此以前,我已經寫了如下幾篇AI基礎的快速入門,本篇文章講解特徵工程基礎第二部分:(數字特徵處理)。github
AI 基礎:Python 簡易入門算法
AI 基礎:Numpy 簡易入門apache
AI 基礎:Pandas 簡易入門json
AI 基礎:Scipy(科學計算庫) 簡易入門數組
AI基礎:數據可視化簡易入門(matplotlib和seaborn)app
AI基礎:特徵工程-類別特徵less
參考資料:dom
[1]原版(英文)圖書地址: 機器學習
https://www.oreilly.com/library/view/feature-engineering-for/9781491953235/
[2]翻譯來源apachecn:
https://github.com/apachecn
[3]翻譯做者@coboe:
代碼修改和整理:黃海廣,原文修改爲jupyter notebook格式,並增長和修改了部分代碼,測試所有經過,全部數據集已經放在百度雲下載。
本文代碼能夠在github下載:
https://github.com/fengdu78/Data-Science-Notes/tree/master/9.feature-engineering
數據集的百度雲:
連接:https://pan.baidu.com/s/1uDXt5jWUOfI0fS7hD91vBQ 提取碼:8p5d
在深刻研究諸如文本和圖像這樣的複雜數據類型以前,讓咱們先從最簡單的數字數據開始。它們可能來自各類來源:地理位置或人、購買的價格、傳感器的測量、交通計數等。數字數據已是數學模型容易消化的格式。這並不意味着再也不須要特徵工程。好的特徵不只表明數據的顯著方面,並且符合模型的假設。所以,轉換經常是必要的。數字特徵工程技術是基礎。當原始數據被轉換爲數字特徵時,它們能夠被應用。
數值數據的第一個健全檢查是大小是否重要。咱們只須要知道它是正面的仍是負面的?或者咱們只須要知道一個很是粗粒度的大小?這一明智的檢查對於自動累積數尤爲重要,好比統計,天天訪問網站的次數,餐館所得到的評論數量等等。
接下來,考慮特徵的規模。最大值和最小值是什麼?它們跨越幾個數量級嗎?輸入特性平滑的模型對輸入的尺度敏感。例如,3x+ 1是輸入X的簡單線性函數,其輸出的規模直接取決於輸入的比例。其餘例子包括k-均值聚類,最近鄰居方法,RBF內核,以及使用歐幾里得距離的任何東西。對於這些模型和建模組件,一般規範化特徵以使輸出保持在預期的規模上一般是一個好主意。
另外一方面,邏輯函數對輸入特徵量表不敏感。不管輸入是什麼,它們的輸出都是二進制的。例如,邏輯,並採起任何兩個變量和輸出1,當且僅當兩個輸入均爲真。邏輯函數的另外一個例子是階躍函數「輸入x大於5」。決策樹模型由輸入特徵的階躍函數組成。所以,基於空間劃分樹(決策樹、梯度提高機、隨機森林)的模型對尺度不敏感。惟一的例外是若是輸入的規模隨着時間的增加而增加,那麼若是該特徵是某種類型的累積計數。最終它將生長在樹被訓練的範圍以外。若是多是這樣的話,那麼就有必要週期性地從新調整輸入。另外一個解決方案是第5章討論的bin計數方法。
考慮數值特徵的分佈也是很重要的。分佈總結了承擔特訂價值的可能性。輸入特徵的分佈對某些模型比其餘模型更重要。例如,線性迴歸模型的訓練過程假定預測偏差分佈得像高斯。這一般是好的,除非預測目標在幾個數量級上擴散。在這種狀況下,高斯偏差假設可能再也不成立。解決這一問題的一種方法是轉變產出目標,以馴服規模的增加。(嚴格地說,這將是目標工程,而不是特徵工程。)對數變換,這是一種功率變換,將變量的分佈接近高斯。另外一個解決方案是第5章討論的bin計數方法。
除了裁剪模型或培訓過程的假設, 多個功能能夠組合成更復雜的功能。但願複雜的功能可以更簡潔地捕捉原始數據中的重要信息。經過使輸入功能更加 "雄辯", 模型自己能夠更簡單, 更容易進行培訓和評估, 並作出更好的預測。做爲一個極端的, 複雜的特色自己多是統計模型的輸出。這是一個稱爲模型堆疊的概念, 咱們將在7章和8章中更詳細地討論。在本章中, 咱們給出了複雜特徵的最簡單示例: 交互功能。
交互特徵易於制定,但特徵的組合致使更多的輸入到模型中。爲了減小計算開銷,一般須要使用自動特徵選擇來修剪輸入特徵。
咱們將從標量、向量和空間的基本概念開始,而後討論尺度、分佈、交互特徵和特徵選擇。
在咱們開始以前, 咱們須要定義一些基本概念, 這本書的其他部分。單個數字特徵也稱爲標量。標量的有序列表稱爲向量。向量位於向量空間中。在絕大多數機器學習應用中, 對模型的輸入一般表示爲數字向量。本書的其他部分將討論將原始數據轉換爲數字向量的最佳實踐策略.
向量能夠被可視化爲空間中的一個點。(有時人們從原點到那一點畫一條線和一個箭頭。在這本書中,咱們將主要使用這一點。例如,假設咱們有一個二維向量
,。也就是說,向量包含兩個數,在第一方向中,向量具備1的值,而且在第二方向中,它具備的值。咱們能夠在二維圖中繪製
。
Figure 2-1. A single vector.
在數據世界中, 抽象向量及其特徵維度具備實際意義。例如, 它能夠表明一我的對歌曲的偏心。每首歌都是一個特徵, 其中1的值至關於大拇指向上,
個拇指向下。假設向量
表示一個聽衆 Bob 的喜愛。Bob喜歡 Bob Dylan 的 「Blowin’ in the Wind」 和 Lady Gaga 的 "Poker Face"。其餘人可能有不一樣的喜愛。總的來講, 數據集合能夠在特徵空間中可視化爲點雲.
相反,一首歌能夠由一組人的我的喜愛來表示。假設只有兩個聽衆,Alice 和 Bob。Alice 喜歡 Leonard Cohen 的 「Poker Face」, 「Blowin’ in the Wind」 和 「Hallelujah」,但討厭 Katy Perry 的 「Roar」 和 Radiohead 的 「Creep」。Bob 喜歡 「Roar", 「Hallelujah」和「Blowin’ in the Wind」,但討厭 「Poker Face」 和 「Creep」 。在聽衆的空間裏,每一首歌都是一個點。就像咱們能夠在特徵空間中可視化數據同樣,咱們能夠在數據空間中可視化特徵。圖2-2顯示了這個例子。
Figure 2-2. Illustration of feature space vs. data space.
在大數據時代,計數能夠快速積累而不受約束。用戶能夠將歌曲或電影放在無限播放中,或者使用腳本反覆檢查流行節目的門票可用性,這會致使播放次數或網站訪問計數迅速上升。當數據能夠以高的體積和速度產生時,它們極可能包含一些極值。這是一個好主意,檢查他們的規模,並肯定是否保持它們做爲原始數字,將它們轉換成二進制變量,以指示存在,或將它們放入粗粒度。
Million Song 數據集中的用戶品味畫像包含了一百萬個用戶在 Echo Nest 的完整音樂聆聽歷史。下面是有關數據集的一些相關統計數據。
假設任務是創建一個推薦器向用戶推薦歌曲。推薦器的一個組件能夠預測用戶將對一首特別的歌曲會有多少喜歡。因爲數據包含實際的聽歌次數,這應該是預測的目標嗎?若是一個大的聽計數意味着用戶真的喜歡這首歌,反之亦然,那是正確的。然而,數據代表,雖然99%的聽計數是24或更低,也有一些聽計數數以千計,最大爲9667。(如圖2-3所示,直方圖最接近於0的bin中的峯值。可是超過10000個三元組的計數更大,幾千個則有幾個。這些值異常大;若是咱們試圖預測實際的聽計數,那麼模型將被這些大的值拉離。
Figure 2-3. Histogram of listen counts in the user taste profile of the Million Song Dataset. Note that the y-axis is on a log scale.
在 Million Song 數據集中,原始監聽計數不是用戶口味的可靠度量。(在統計術語中,健壯性意味着該方法在各類各樣的條件下工做。)用戶有不一樣的聽力習慣。有些人可能把他們最喜歡的歌曲放在無限的循環中,而其餘人可能只在特殊的場合品嚐它們。很難說聽一首歌20次的人必定喜歡聽10次的人的兩倍。
用戶偏好的更健壯表示是使計數二元化和修剪全部大於1的計數爲1。換句話說,若是用戶至少聽過一首歌,那麼咱們將其視爲用戶喜歡歌曲。這樣,模型不須要花費週期來預測原始計數之間的微小差別。二進制目標是用戶偏好的簡單而穩健的度量。
import pandas as pd listen_count = pd.read_csv( 'data/train_triplets.txt.zip', header=None, delimiter='\t') # The table contains user-song-count triplets. Only non-zero counts are # included. Hence to binarize the count, we just need to set the entire # count column to 1. listen_count[2] = 1
這是咱們設計模型目標變量的一個例子。嚴格地說, 目標不是一個特徵, 由於它不是輸入。但有時咱們確實須要修改目標以解決正確的問題。
對於本練習, 咱們從第 6 輪 Yelp 數據集挑戰中採集數據, 並建立一個更小的分類數據集。Yelp 數據集包含用戶對來自北美和歐洲十個城市的企業的評論。每一個商戶都標記爲零個或多個類別。如下是有關數據集的相關統計信息。
每一個商戶都有一個評論計數。假設咱們的任務是使用協同過濾來預測用戶可能給企業的評級。評論計數多是一個有用的輸入特徵,由於一般在流行和良好的評級之間有很強的相關性。如今的問題是,咱們應該使用原始評論計數或進一步處理它嗎?圖2-4顯示了全部商戶評論計數的直方圖。咱們看到和音樂聽歌計數同樣的模式。大部分的統計數字都很小,但一些企業有成千上萬的評論。
import pandas as pd import json import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline
### Load the data about businesses biz_file = open('data/yelp_academic_dataset_business.json') biz_df = pd.DataFrame([json.loads(x) for x in biz_file.readlines()]) biz_file.close()
### Plot the histogram of the review counts sns.set_style('whitegrid') fig, ax = plt.subplots() biz_df['review_count'].hist(ax=ax, bins=100) ax.set_yscale('log') ax.tick_params(labelsize=14) ax.set_xlabel('Review Count', fontsize=14) ax.set_ylabel('Occurrence', fontsize=14)
Text(0,0.5,'Occurrence')
對於許多模型來講,跨越數個數量級的原始計數是有問題的。在線性模型中,相同的線性係數必須對計數的全部可能值工做。大量的計數也可能破壞無監督學習方法,如k-均值聚類,它使用類似性函數來測量數據點之間的類似性。k-均值使用數據點之間的歐幾里得距離。數據向量的一個元素中的大計數將超過全部其餘元素中的類似性,這可能會丟棄整個類似性度量。
一種解決方案是經過量化計數來包含標量。換句話說,咱們將計數分組到容器中,而且去掉實際的計數值。量化將連續數映射成離散數。咱們能夠把離散化的數字看做是表明強度度量的容器的有序的序列。
爲了量化數據,咱們必須決定每個箱子應該有多寬。解決方案分爲固定寬度或自適應兩種類型。咱們將給出每一個類型的例子。
對於固定寬度裝箱, 每一個 bin 都包含一個特定的數值範圍。範圍能夠是定製設計或自動分割, 它們能夠線性縮放或指數縮放。例如, 咱們能夠將一我的的年齡分組爲十年: 0-9 歲概括到bin 1, 10-19 年概括到 bin 2 等。要從計數映射到 bin, 只需除以 bin 的寬度並取整部分。
也常常看到定製設計的年齡範圍更適合於生活的階段:
當數字跨越多個數量級時,最好用10個冪(或任何常數的冪)來分組:0-九、10-9九、100-99九、100-9999等。容器寬度呈指數增加,從O(10)、O(100)到O(1000)和以上。要從計數映射到bin,取計數的log值。指數寬度的劃分與對數變換很是相關,咱們在「對數變換」中討論。
import numpy as np ### Generate 20 random integers uniformly between 0 and 99 small_counts = np.random.randint(0, 100, 20) small_counts
array([84, 45, 51, 18, 50, 78, 40, 25, 75, 17, 39, 44, 69, 53, 35, 5, 8,
51, 63, 34])
np.floor_divide(small_counts, 10)
array([8, 4, 5, 1, 5, 7, 4, 2, 7, 1, 3, 4, 6, 5, 3, 0, 0, 5, 6, 3],
dtype=int32)
### An array of counts that span several magnitudes large_counts = [ 296, 8286, 64011, 80, 3, 725, 867, 2215, 7689, 11495, 91897, 44, 28, 7971, 926, 122, 22222 ]
### Map to exponential-width bins via the log function np.floor(np.log10(large_counts))
array([2., 3., 4., 1., 0., 2., 2., 3., 3., 4., 4., 1., 1., 3., 2., 2., 4.])
固定寬度裝箱很容易計算。可是若是計數有很大的差距, 那麼將會有許多空的垃圾箱沒有數據。該問題能夠經過基於數據分佈的垃圾箱自適應定位來解決。這可使用分發的分位數來完成。
分位數是將數據劃分爲相等部分的值。例如, 中位數將數據分紅一半;一半的數據是較小的, 一半大於中位數。分位數把數據分紅幾個部分, 十分位數把數據劃分紅十份。示例2-4 演示如何計算 Yelp 商戶評論數的十等分, 圖2-5 覆蓋直方圖上的十等分。這就更清楚地說明了對更小的計數的歪斜。
deciles = biz_df['review_count'].quantile([.1, .2, .3, .4, .5, .6, .7, .8, .9]) deciles
0.1 3.0
0.2 3.0
0.3 4.0
0.4 5.0
0.5 6.0
0.6 8.0
0.7 12.0
0.8 23.0
0.9 50.0
Name: review_count, dtype: float64
### Visualize the deciles on the histogram sns.set_style('whitegrid') fig, ax = plt.subplots() biz_df['review_count'].hist(ax=ax, bins=100) for pos in deciles: handle = plt.axvline(pos, color='r') ax.legend([handle], ['deciles'], fontsize=14) ax.set_yscale('log') ax.set_xscale('log') ax.tick_params(labelsize=14) ax.set_xlabel('Review Count', fontsize=14) ax.set_ylabel('Occurrence', fontsize=14)
Text(0,0.5,'Occurrence')
Figure 2-5. Deciles of the review counts in the Yelp reviews dataset. Note that both x- and y-axes are in log scale.
爲了計算分位數和映射數據到分位數箱,咱們可使用 Pandas 庫。pandas.DataFrame.quantile 和 pandas.Series.quantile 用於計算分位數。pandas.qcut將數據映射到所需數量的分位數。
### Continue example Example 2-3 with large_counts import pandas as pd ### Map the counts to quartiles pd.qcut(large_counts, 4, labels=False)
array([1, 2, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 2, 1, 0, 3], dtype=int64)
### Compute the quantiles themselves large_counts_series = pd.Series(large_counts) large_counts_series.quantile([0.25, 0.5, 0.75])
0.25 122.0
0.50 926.0
0.75 8286.0
dtype: float64
在「量化或裝箱」中,咱們簡要地介紹了把計數的對數映射到指數寬度箱的概念。讓咱們如今再看一看。
對數函數是指數函數的逆。它定義爲
其中 爲正常數, 能夠是任何正數。因爲,咱們有。這意味着對數函數將小範圍的數字 (0、1) 映射到負數的整個範圍。函數 將 、 映射到 、、將、 映射到 、 等等。換言之, 對數函數壓縮大數的範圍, 並擴展小數的範圍。越大的 , 的增量越慢。經過查看
函數的圖像, 能夠更容易地消化這一點。(見圖2-6)。
Figure 2-6. The log function compresses the high numeric range and expands the low range. Note how the horizontal x values from 100 to 1000 got compressed into just 2.0 to 3.0 in the vertical y range, while the tiny horizontal portion of x values less than 100 are mapped to the rest of the vertical range.
對數變換是處理具備重尾分佈的正數的有力工具。(重尾分佈在尾部範圍內的機率比高斯分佈的機率大)。它將分佈在高端的長尾壓縮成較短的尾部,並將低端擴展成較長的頭部。圖2-7比較d對數轉換以前和以後的YELP商戶評論計數的直方圖。Y軸如今都在正常(線性)尺度上。在
範圍內的底部圖中增長的倉間隔是因爲在1和10之間只有10個可能的整數計數。請注意,原始審查計數很是集中在低計數區域,離羣值在4000以上。對數變換後,直方圖不集中在低端,更分散在X軸上。
例2-6:可視化對數變換先後評論數分佈
fig, (ax1, ax2) = plt.subplots(2,1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) biz_df['review_count'].hist(ax=ax1, bins=100) ax1.tick_params(labelsize=14) ax1.set_xlabel('review_count', fontsize=14) ax1.set_ylabel('Occurrence', fontsize=14) biz_df['log_review_count'] = np.log(biz_df['review_count'] + 1) biz_df['log_review_count'].hist(ax=ax2, bins=100) ax2.tick_params(labelsize=14) ax2.set_xlabel('log10(review_count))', fontsize=14) ax2.set_ylabel('Occurrence', fontsize=14)
Text(23.625,0.5,'Occurrence')
Figure 2-7. Comparison of Yelp business review counts before (top) and after (bottom) log transformation.
另外一個例子是來自 UC Irvine 機器學習庫的在線新聞流行數據集。如下是有關數據集的相關統計信息。
在線新聞流行數據集的統計
目的是利用這些特徵來預測文章在社交媒體上的用分享數量表示的流行度。在本例中, 咱們將只關注一個特徵——文章中的單詞數。圖2-8 顯示了對數轉換先後特徵的直方圖。請注意, 在對數轉換後, 分佈看起來更高斯, 除了長度爲零的文章 (無內容) 的斷裂。
df = pd.read_csv('data/OnlineNewsPopularity.csv', delimiter=', ')
df.head()
df['log_n_tokens_content'] = np.log10(df['n_tokens_content'] + 1)
fig, (ax1, ax2) = plt.subplots(2,1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) df['n_tokens_content'].hist(ax=ax1, bins=100) ax1.tick_params(labelsize=14) ax1.set_xlabel('Number of Words in Article', fontsize=14) ax1.set_ylabel('Number of Articles', fontsize=14) df['log_n_tokens_content'].hist(ax=ax2, bins=100) ax2.tick_params(labelsize=14) ax2.set_xlabel('Log of Number of Words', fontsize=14) ax2.set_ylabel('Number of Articles', fontsize=14)
Text(23.625,0.5,'Number of Articles')
Figure 2-8. Comparison of word counts in Mashable news articles before (top) and after (bottom) log transformation.
讓咱們看看在監督學習中對數轉換如何執行。咱們將使用上面的兩個數據集。對於 Yelp 評論數據集, 咱們將使用評論的數量來預測商戶的平均評級。對於 Mashable 的新聞文章, 咱們將使用文章中的字數來預測其流行程度。因爲輸出是連續的數字, 咱們將使用簡單的線性迴歸做爲模型。咱們在沒有對數變換和有對數變換的特點上,使用 Scikit Learn 執行10折交叉驗證的線性迴歸。模型由 R 方評分來評估, 它測量訓練後的迴歸模型預測新數據的良好程度。好的模型有較高的 R 方分數。一個完美的模型獲得最高分1。分數能夠是負的, 一個壞的模型能夠獲得一個任意低的負評分。經過交叉驗證, 咱們不只獲得了分數的估計, 還得到了方差, 這有助於咱們判斷兩種模型之間的差別是否有意義。
import pandas as pd import numpy as np import json from sklearn import linear_model from sklearn.model_selection import cross_val_score
## Using the previously loaded Yelp reviews dataframe, ## compute the log transform of the Yelp review count. ## Note that we add 1 to the raw count to prevent the logarithm from ## exploding into negative infinity in case the count is zero. biz_df['log_review_count'] = np.log10(biz_df['review_count'] + 1)
biz_df.head()
## Train linear regression models to predict the average stars rating of a business, ## using the review_count feature with and without log transformation ## Compare the 10-fold cross validation score of the two models m_orig = linear_model.LinearRegression() scores_orig = cross_val_score( m_orig, biz_df[['review_count']], biz_df['stars'], cv=10) m_log = linear_model.LinearRegression() scores_log = cross_val_score( m_log, biz_df[['log_review_count']], biz_df['stars'], cv=10) print("R-squared score without log transform: %0.5f (+/- %0.5f)" % (scores_orig.mean(), scores_orig.std() * 2)) print("R-squared score with log transform: %0.5f (+/- %0.5f)" % (scores_log.mean(), scores_log.std() * 2))
R-squared score without log transform: 0.00215 (+/- 0.00329)
R-squared score with log transform: 0.00136 (+/- 0.00328)
從實驗的結果來看, 兩個簡單的模型 (有對數變換和沒有對數變換) 在預測目標時一樣很差, 而有對數變換的特徵表現略差。真使人失望!這並不奇怪, 他們都不是很好, 由於他們都只使用一個功能。可是, 人們原本但願日誌轉換的功能執行得更好。
讓咱們看看對數轉換在線新聞流行數據集上如何表現。
## Download the Online News Popularirty dataset from UCI, then use ## Pandas to load the file into a dataframe ## Take the log transform of the 'n_tokens_content' feature, which ## represents the number of words (tokens) in a news article. df['log_n_tokens_content'] = np.log10(df['n_tokens_content'] + 1) ## Train two linear regression models to predict the number of shares ## of an article, one using the original feature and the other the ## log transformed version. m_orig = linear_model.LinearRegression() scores_orig = cross_val_score( m_orig, df[['n_tokens_content']], df['shares'], cv=10) m_log = linear_model.LinearRegression() scores_log = cross_val_score( m_log, df[['log_n_tokens_content']], df['shares'], cv=10) print("R-squared score without log transform: %0.5f (+/- %0.5f)" % (scores_orig.mean(), scores_orig.std() * 2)) print("R-squared score with log transform: %0.5f (+/- %0.5f)" % (scores_log.mean(), scores_log.std() * 2))
R-squared score without log transform: -0.00242 (+/- 0.00509)
R-squared score with log transform: -0.00114 (+/- 0.00418)
置信區間仍然重疊,但具備對數變換特徵的模型比沒有對數變換的表現更好。爲何對數轉換在這個數據集上更成功?咱們能夠經過觀察輸入特徵和目標值的散點圖來獲得線索。如圖2-9的底部面板所示,對數變換重塑了
軸,將目標值(大於200000個份額)中的大離羣值進一步拉向軸的右手側。這給線性模型在輸入特徵空間的低端更多的「呼吸空間」。沒有對數轉換(上部面板),在輸入值變化下很是小的狀況下,模型有更大的壓力下適應很是不一樣的目標值。
fig2, (ax1, ax2) = plt.subplots(2, 1,figsize=(10, 4)) fig.tight_layout(pad=0.4, w_pad=4.0, h_pad=6.0) ax1.scatter(df['n_tokens_content'], df['shares']) ax1.tick_params(labelsize=14) ax1.set_xlabel('Number of Words in Article', fontsize=14) ax1.set_ylabel('Number of Shares', fontsize=14) ax2.scatter(df['log_n_tokens_content'], df['shares']) ax2.tick_params(labelsize=14) ax2.set_xlabel('Log of the Number of Words in Article', fontsize=14) ax2.set_ylabel('Number of Shares', fontsize=14)
Text(0,0.5,'Number of Shares')
Figure 2-9. Scatter plot of number of words (input) vs. number of shares (target) in the Online News dataset. The top plot visualizes the original feature, and the bottom plot shows the scatter plot after log transformation.
將此與應用於YELP評論數據集的相同散點圖進行比較。圖2-10看起來與圖2-9很是不一樣。在1到5,步長0.5的區間,平均星級是離散的。高評論計數(大體>2500評論)與較高的平均星級評級相關。但這種關係遠不是線性的。沒有一種清晰的方法能夠根據輸入來預測平均星級。從本質上講,該圖代表,評論數及其對數都是平均星級的不良線性預測因子。
### 例2-11。可視化 Yelp 商戶評論預測中輸入與輸出的相關性。 fig, (ax1, ax2) = plt.subplots(2,1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) ax1.scatter(biz_df['review_count'], biz_df['stars']) ax1.tick_params(labelsize=14) ax1.set_xlabel('Review Count', fontsize=14) ax1.set_ylabel('Average Star Rating', fontsize=14) ax2.scatter(biz_df['log_review_count'], biz_df['stars']) ax2.tick_params(labelsize=14) ax2.set_xlabel('Log of Review Count', fontsize=14) ax2.set_ylabel('Average Star Rating', fontsize=14)
Text(23.625,0.5,'Average Star Rating')
Figure 2-10. Scatter plot of review counts (input) vs. average star ratings (target) in the Yelp Reviews dataset. The top panel plots the original review count, and the bottom panel plots the review count after log transformation.
對數變換在兩個不一樣數據集上的影響的比較,說明了可視化數據的重要性。在這裏,咱們故意保持輸入和目標變量簡單,以便咱們能夠很容易地可視化它們之間的關係。如圖2-10所示的曲線,當即顯示所選擇的模型(線性)不可能表明所選擇的輸入和目標之間的關係。另外一方面,人們能夠使人信服地在給定平均星級模擬評論數的分佈。在創建模型時,最好直觀地檢查輸入和輸出之間的關係,以及不一樣輸入特徵之間的關係。
對數變換是一個稱爲功率變換的變換族的特殊例子。在統計方面,這些是方差穩定的變換。爲了理解爲何方差穩定是好的,考慮泊松分佈。這是一個方差等於它的平均值的重尾分佈。所以,它的質量中心越大,其方差越大,尾部越重。功率變換改變變量的分佈,使得方差再也不依賴於平均值。例如,假設一個隨機變量X具備泊松分佈。假如咱們使用開平方根變換
,
的方差大體是恆定的, 而不是等於平均值。
Figure 2-11. A rough illustration of the Poisson distribution. λ represents the mean of the distribution. As λ increases, not only does the mode of of the distribution shift to the right, but the mass spreads out and the variance becomes larger. The Poisson distribution is an example distribution where the variance increases along with the mean.
平方根變換和對數變換的簡單推廣稱爲Box-Cox變換:
圖2-12, 展現出了在
(log變換),,(平方根的縮放和移位版本),, 和時的Box-Cox變換。設置 小於1時壓縮較高的值,而且設置
大於1時具備相反的效果。
Figure 2-12. Box-Cox transforms for different values of
.
只有當數據爲正值時, Box-Cox 公式才能工做。對於非正數據, 能夠經過加上固定常量來移動數值。當應用 Box-Cox 變換或更通常的功率變換時, 咱們必須肯定參數
的值。這多是經過最大似然(找到的
,使產生的變換信號的高斯似然最大) 或貝葉斯方法。徹底介紹 Box-Cox 和通常功率變換的使用超出了本書的範圍。感興趣的讀者能夠經過 Jack Johnston 和John DiNardo (McGraw Hill) 編寫的Econometric Methods 找到更多關於冪轉換的信息。幸運的是, Scipy 的數據包包含了一個 Box-Cox 轉換的實現, 其中包括查找最佳變換參數。
from scipy import stats # Continuing from the previous example, assume biz_df contains # the Yelp business reviews data # Box-Cox transform assumes that input data is positive. # Check the min to make sure. biz_df['review_count'].min()
3
biz_df.columns
Index(['business_id', 'categories', 'city', 'full_address', 'latitude',
'longitude', 'name', 'neighborhoods', 'open', 'review_count', 'stars',
'state', 'type', 'log_review_count'],
dtype='object')
# Setting input parameter lmbda to 0 gives us the log transform (without constant offset) rc_log = stats.boxcox(biz_df['review_count'], lmbda=0)
biz_df['rc_log']=rc_log
# By default, the scipy implementation of Box-Cox transform finds the lmbda parameter # that will make the output the closest to a normal distribution rc_bc, bc_params = stats.boxcox(biz_df['review_count']) bc_params
-0.5631160899391674
biz_df['rc_bc']=rc_bc
圖2-13 提供了原始和轉換評論數分佈的可視化比較。
### 例2-13。可視化評論數的原始、對數轉換和 Box-Cox 轉換的直方圖。 fig, (ax1, ax2, ax3) = plt.subplots(3, 1) fig.tight_layout(pad=0, w_pad=4.0, h_pad=4.0) # original review count histogram biz_df['review_count'].hist(ax=ax1, bins=100) ax1.set_yscale('log') ax1.tick_params(labelsize=14) ax1.set_title('Review Counts Histogram', fontsize=14) ax1.set_xlabel('') ax1.set_ylabel('Occurrence', fontsize=14) # review count after log transform biz_df['rc_log'].hist(ax=ax2, bins=100) ax2.set_yscale('log') ax2.tick_params(labelsize=14) ax2.set_title('Log Transformed Counts Histogram', fontsize=14) ax2.set_xlabel('') ax2.set_ylabel('Occurrence', fontsize=14) # review count after optimal Box-Cox transform biz_df['rc_bc'].hist(ax=ax3, bins=100) ax3.set_yscale('log') ax3.tick_params(labelsize=14) ax3.set_title('Box-Cox Transformed Counts Histogram', fontsize=14) ax3.set_xlabel('') ax3.set_ylabel('Occurrence', fontsize=14)
Text(29.125,0.5,'Occurrence')
Figure 2-13. Box-Cox transformation of Yelp business review counts.
機率圖是一種直觀地比較數據分佈與理論分佈的簡單方法。這本質上是觀察到散點圖的與理論分位數。圖2-14顯示YELP評論數的原始數據和轉換後數據相對正態分佈的機率圖。因爲觀測數據是嚴格正的,高斯能夠是負的,因此分位數在負端上永遠不會匹配。因此咱們關注的是正數這的一邊。在這方面,原始評論數明顯比正常分佈更重尾。(有序值上升到4000,而理論位數僅延伸到4)。簡單的對數變換和最優的 Box-Cox 變換都使正尾部接近正態分佈。最優的 Box-Cox 變換比對數變換更縮小尾部,因爲尾部在紅色對角線等值線下平展能夠明顯看出。
fig2, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 6)) # fig.tight_layout(pad=4, w_pad=5.0, h_pad=0.0) prob1 = stats.probplot(biz_df['review_count'], dist=stats.norm, plot=ax1) ax1.set_xlabel('') ax1.set_title('Probplot against normal distribution') prob2 = stats.probplot(biz_df['rc_log'], dist=stats.norm, plot=ax2) ax2.set_xlabel('') ax2.set_title('Probplot after log transform') prob3 = stats.probplot(biz_df['rc_bc'], dist=stats.norm, plot=ax3) ax3.set_xlabel('Theoretical quantiles') ax3.set_title('Probplot after Box-Cox transform')
Text(0.5,1,'Probplot after Box-Cox transform')
Figure 2-14. Comparing the distribution of raw and transformed review counts against the Normal distribution.
某些特徵的值有界的,如緯度或經度。其餘數值特徵 (如數量) 可能會在***的狀況下增長。那些關於輸入是平滑函數的模型, 如線性迴歸、邏輯迴歸或任何涉及矩陣的東西, 都受輸入的數值範圍影響。另外一方面, 基於樹的模型不太在乎這個。若是你的模型對輸入特徵的數值範圍敏感, 則特徵縮放可能會有所幫助。顧名思義, 特徵縮放會更改特徵值的數值範圍。有時人們也稱它爲特徵規範化。功能縮放一般分別針對單個特徵進行。有幾種常見的縮放操做, 每一個類型都產生不一樣的特徵值分佈。
最小最大縮放和標準化都從原始特徵值中減去一個數量。對於最小最大縮放, 移動量是當前特徵的全部值中最小的。對於標準化, 移動的量是平均值。若是移動量不是零, 則這兩種轉換能夠將稀疏特徵(大部分值爲零)的向量轉換爲一個稠密的向量。這反過來會給分類器帶來巨大的計算負擔, 取決於它是如何實現的。詞袋是一種稀疏表示, 大多數分類庫都對稀疏輸入進行優化。若是如今的表示形式包含了文檔中沒有出現的每一個單詞, 那就太可怕了。請謹慎對稀疏特徵執行最小最大縮放和標準化操做。
這項技術經過所謂的 L2 範數 (也稱爲歐幾里德範數) 正常化 (劃分) 原始特徵值。
請注意,圖2-17中的說明是在數據空間中,而不是特徵空間。還能夠對數據點進行L2歸一化,而不是特徵,這將致使具備單位範數(範數爲1)的數據向量。(參見詞袋中關於數據向量和特徵向量的互補性質的討論)無論縮放方法如何,特徵縮放老是將特徵除以常數(也稱爲歸一化常數)。所以,它不會改變單特徵分佈的形狀。咱們將用在線新聞文章標記計數來講明這一點。
import pandas as pd import sklearn.preprocessing as preproc # Load the online news popularity dataset df = pd.read_csv('data/OnlineNewsPopularity.csv', delimiter=', ') # Look at the original data - the number of words in an article df['n_tokens_content'].as_matrix()
array([219., 255., 211., ..., 442., 682., 157.])
# Min-max scaling df['minmax'] = preproc.minmax_scale(df[['n_tokens_content']]) df['minmax'].as_matrix()
array([0.02584376, 0.03009205, 0.02489969, ..., 0.05215955, 0.08048147,
0.01852726])
# Standardization - note that by definition, some outputs will be negative df['standardized'] = preproc.StandardScaler().fit_transform(df[['n_tokens_content']]) df['standardized'].as_matrix()
array([-0.69521045, -0.61879381, -0.71219192, ..., -0.2218518 ,
0.28759248, -0.82681689])
# L2-normalization df['l2_normalized'] = preproc.normalize(df[['n_tokens_content']], axis=0) df['l2_normalized'].as_matrix()
array([0.00152439, 0.00177498, 0.00146871, ..., 0.00307663, 0.0047472 ,
0.00109283])
咱們也能夠可視化用不一樣的特徵縮放方法後的數據的分佈。如圖2-18所示,與對數變換不一樣,特徵縮放不會改變分佈的形狀;只有數據的規模發生變化。
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4,1) fig.tight_layout(pad=0, w_pad=1.0, h_pad=2.0) # fig.tight_layout() df['n_tokens_content'].hist(ax=ax1, bins=100) ax1.tick_params(labelsize=14) ax1.set_xlabel('Article word count', fontsize=14) # ax1.set_ylabel('Number of articles', fontsize=14) df['minmax'].hist(ax=ax2, bins=100) ax2.tick_params(labelsize=14) ax2.set_xlabel('Min-max scaled word count', fontsize=14) ax2.set_ylabel('Number of articles', fontsize=14) df['standardized'].hist(ax=ax3, bins=100) ax3.tick_params(labelsize=14) ax3.set_xlabel('Standardized word count', fontsize=14) # ax3.set_ylabel('Number of articles', fontsize=14) df['l2_normalized'].hist(ax=ax4, bins=100) ax4.tick_params(labelsize=14) ax4.set_xlabel('L2-normalized word count', fontsize=14) ax4.set_ylabel('Number of articles', fontsize=14)
Text(29.125,0.5,'Number of articles')
Figure 2-18. Original and scaled news article word counts. Note that only the scale of the x-axis changes; the shape of the distribution stays the same with feature scaling.
在一組輸入特徵在比例上差別很大的狀況下,特徵縮放很是有用。例如,一個流行的電子商務網站的每日訪問者數量多是十萬,而實際銷售額多是幾千。若是這兩種功能都投入到模型中,那麼該模型須要在肯定要作什麼的同時平衡它們的規模。輸入特徵的極大變化會致使模型訓練算法的數值穩定性問題。在這些狀況下,標準化功能是個好主意。第4章將詳細介紹處理天然文本時的特徵縮放,包括使用示例。
簡單的成對交互特徵是兩個特徵的積。相似邏輯與。它以成對條件表示結果:「購買來自郵政編碼98121」和「用戶的年齡在18到35之間」。這一點對基於決策樹的模型沒有影響,但發交互特徵對廣義線性模型一般頗有幫助。
。
在例2-17中,咱們使用 UCI 在線新聞數據集中的成對交互特徵來預測每篇新聞文章的分享數量。交互特徵致使精度超過單身特徵。二者都比例2-9表現得更好,它使用文章正文中單詞數的單個預測器(有或沒有通過對數變換)。
from sklearn import linear_model from sklearn.model_selection import train_test_split import sklearn.preprocessing as preproc ### Assume df is a Pandas dataframe containing the UCI online news dataset df.columns
Index(['url', 'timedelta', 'n_tokens_title', 'n_tokens_content',
'n_unique_tokens', 'n_non_stop_words', 'n_non_stop_unique_tokens',
'num_hrefs', 'num_self_hrefs', 'num_imgs', 'num_videos',
'average_token_length', 'num_keywords', 'data_channel_is_lifestyle',
'data_channel_is_entertainment', 'data_channel_is_bus',
'data_channel_is_socmed', 'data_channel_is_tech',
'data_channel_is_world', 'kw_min_min', 'kw_max_min', 'kw_avg_min',
'kw_min_max', 'kw_max_max', 'kw_avg_max', 'kw_min_avg', 'kw_max_avg',
'kw_avg_avg', 'self_reference_min_shares', 'self_reference_max_shares',
'self_reference_avg_sharess', 'weekday_is_monday', 'weekday_is_tuesday',
'weekday_is_wednesday', 'weekday_is_thursday', 'weekday_is_friday',
'weekday_is_saturday', 'weekday_is_sunday', 'is_weekend', 'LDA_00',
'LDA_01', 'LDA_02', 'LDA_03', 'LDA_04', 'global_subjectivity',
'global_sentiment_polarity', 'global_rate_positive_words',
'global_rate_negative_words', 'rate_positive_words',
'rate_negative_words', 'avg_positive_polarity', 'min_positive_polarity',
'max_positive_polarity', 'avg_negative_polarity',
'min_negative_polarity', 'max_negative_polarity', 'title_subjectivity',
'title_sentiment_polarity', 'abs_title_subjectivity',
'abs_title_sentiment_polarity', 'shares', 'minmax', 'standardized',
'l2_normalized'],
dtype='object')
### Select the content-based features as singleton features in the model, ### skipping over the derived features features = ['n_tokens_title', 'n_tokens_content', 'n_unique_tokens', 'n_non_stop_words', 'n_non_stop_unique_tokens', 'num_hrefs', 'num_self_hrefs', 'num_imgs', 'num_videos', 'average_token_length', 'num_keywords', 'data_channel_is_lifestyle', 'data_channel_is_entertainment', 'data_channel_is_bus', 'data_channel_is_socmed', 'data_channel_is_tech', 'data_channel_is_world'] X = df[features] y = df[['shares']]
### Create pairwise interaction features, skipping the constant bias term X2 = preproc.PolynomialFeatures(include_bias=False).fit_transform(X) X2.shape
(39644, 170)
### Create train/test sets for both feature sets X1_train, X1_test, X2_train, X2_test, y_train, y_test = train_test_split(X, X2, y, test_size=0.3, random_state=123) def evaluate_feature(X_train, X_test, y_train, y_test): # Fit a linear regression model on the training set and score on the test set model = linear_model.LinearRegression().fit(X_train, y_train) r_score = model.score(X_test, y_test) return (model, r_score)
### Train models and compare score on the two feature sets (m1, r1) = evaluate_feature(X1_train, X1_test, y_train, y_test) (m2, r2) = evaluate_feature(X2_train, X2_test, y_train, y_test) print("R-squared score with singleton features: %0.5f" % r1) print("R-squared score with pairwise features: %0.10f" % r2)
R-squared score with singleton features: 0.00924
R-squared score with pairwise features: 0.0113280910
構造交互特徵很是簡單,但它們使用起來很昂貴。使用成對交互特徵的線性模型的訓練和得分時間將從
到,其中
是單身特徵的數量。
圍繞高階交互特徵的計算成本有幾種方法。能夠在全部交互特徵之上執行特徵選擇,選擇前幾個。或者,能夠更仔細地製做更少數量的複雜特徵。兩種策略都有其優勢和缺點。特徵選擇採用計算手段來選擇問題的最佳特徵。(這種技術不限於交互特徵。)一些特徵選擇技術仍然須要訓練多個具備大量特徵的模型。
手工製做的複雜特徵能夠具備足夠的表現力,因此只須要少許這些特徵,這能夠縮短模型的訓練時間。可是特徵自己的計算可能很昂貴,這增長了模型評分階段的計算成本。手工製做(或機器學習)的複雜特徵的好例子能夠在第8章有關圖像特徵中找到。如今讓咱們看看一些特徵選擇技巧。
特徵選擇技術會刪除非有用的特徵,以下降最終模型的複雜性。最終目標是快速計算的簡約模型,預測準確性下降很小或不會下降。爲了獲得這樣的模型,一些特徵選擇技術須要訓練多個候選模型。換句話說,特徵選擇並非減小訓練時間,實際上有些技巧增長了總體訓練時間,可是減小了模型評分時間。
粗略地說,特徵選擇技術分爲三類。
Filtering(過濾): 預處理能夠刪除那些不太可能對模型有用的特徵。例如,能夠計算每一個特徵與響應變量之間的相關或相互信息,並篩除相關信息或相互信息低於閾值的特徵。第3章討論了文本特徵的過濾技術的例子。過濾比下面的包裝(wrapper)技術便宜得多,可是他們沒有考慮到正在使用的模型。所以他們可能沒法爲模型選擇正確的特徵。最好先保守地進行預過濾,以避免在進行模型訓練步驟以前無心中消除有用的特徵。
特徵選擇的全面處理超出了本書的範圍。有興趣的讀者能夠參考 Isabelle Guyon 和 André Elisseeff 撰寫的調查報告「變量和特徵選擇介紹」(「An Introduction to Variable and Feature Selection」)。
本文討論了許多常見的數字特徵工程技術:量化,縮放(又稱規範化),對數變換(一種功率變換),交互特徵以及處理大量交互特徵所需的特徵選擇技術的簡要總結。在統計機器學習中,全部數據最終歸結爲數字特徵。所以,全部道路最終都會指向某種數字特徵工程技術。爲告終束特徵工程這個遊戲,保證這些工具方便使用!
Guyon, Isabell, and André Elisseeff. 2003. Journal of Machine Learning Research Special Issue on Variable and Feature Selection. 3(Mar):1157--1182.
Johnston, Jack, and John DiNardo. 1997. Econometric Methods (Fourth Edition). New York: McGraw Hill.
原版(英文)圖書地址:
https://www.oreilly.com/library/view/feature-engineering-for/9781491953235/
本文代碼能夠在github下載:
https://github.com/fengdu78/Data-Science-Notes/tree/master/9.feature-engineering
數據集的百度雲:
連接:https://pan.baidu.com/s/1uDXt5jWUOfI0fS7hD91vBQ 提取碼:8p5d