用转换器抽取特征
特征抽取
特征抽取是數據挖掘任務為重要的一個環節,一般而言,它對終結果的影響要高過數據挖掘算法本身。不幸的是,關于怎樣選取好的特征,還沒有嚴格、快捷的規則可循,其實這也是數據挖掘科學更像是一門藝術的所在。創建好的規則離不開直覺,還需要專業領域知識和數據挖 掘經驗,光有這些還不夠,還得不停地嘗試、摸索,在試錯中前進,有時多少還要靠點運氣。
不是所有的數據集都是用特征來表示的。數據集可以是一位作家所寫的全部書籍,也可以是1979年以來所有電影的膠片,還可以是館藏的一批歷史文物。
我們可能想對以上數據集做數據挖掘。對于作家的作品,我們想知道這位作家都寫過哪些主題;對于電影,我們想知道女性形象是怎么塑造的;對于文物,我們想知道它們是何方產物。我們沒辦法把實物直接塞進決策樹來得到結果。
只有先把現實用特征表示出來,才能借助數據挖掘的力量找到問題的答案。特征可用于建模, 模型以機器挖掘算法能夠理解的近似的方式來表示現實。因此可以說,模型描述了客觀世界中對 象的某些方面,是它的一個簡化版本。舉例來說,象棋就是對過往戰事簡化后得到的一種模型。
特征選擇的另一個優點在于:降低真實世界的復雜度,模型比現實更容易操縱。實物的復雜性對目前的算法而言過于復雜,我們退而求其次,使用更為簡潔的模型來表示實物。
我們應該始終關注怎么用模型來表示現實,要多考慮數據挖掘的目標,而不是輕率地用我們過去用過的特征。要經常想想我們的目標是什么。
年收入預測
這里,我們預測一個人年收入是否會多于5萬美元。使用Adult數據集來為復雜現實世界建模。數據集的下載地址:http://archive.ics.uci.edu/ml/datasets/Adult
點擊Data Folder鏈接。下載adult.data和adult.names文件,在數據集文件夾(主目錄下Data文件夾)中創建Adult文件夾,將這兩個文件放到里面。
我們來導入數據:
import os import pandas as pd data_folder = os.path.join(os.path.expanduser("~"), "Data", "Adult") adult_filename = os.path.join(data_folder, "adult.data")adult = pd.read_csv(adult_filename, header=None, names=["Age", "Work-Class", "fnlwgt", "Education","Education-Num", "Marital-Status", "Occupation","Relationship", "Race", "Sex", "Capital-gain","Capital-loss", "Hours-per-week", "Native-Country","Earnings-Raw"])adult.data文件末尾有兩處空行。pandas默認把倒數第二行空行作為一條有效數據讀入(只不過各列的值為空)。我們需要刪除包含無效數字的行(設置inplace參數為真,表示改動當前數據框,而不是新建一個)
刪除表中的某一行或者某一列更明智的方法是使用drop,它不改變原有的df中的數據,而是返回另一個dataframe來存放刪除后的數據。
# 將全部項都是nan的row刪除 adult.dropna(how='all', inplace=True)下面查看所有保存在pandas中Index中的特征名
adult.columnsIndex([‘Age’, ‘Work-Class’, ‘fnlwgt’, ‘Education’, ‘Education-Num’,
‘Marital-Status’, ‘Occupation’, ‘Relationship’, ‘Race’, ‘Sex’,
‘Capital-gain’, ‘Capital-loss’, ‘Hours-per-week’, ‘Native-Country’,
‘Earnings-Raw’],
dtype=’object’)
通用特征創建模式
我們導入的數據集,包含連續特征、序數特征。例如,Hours-per-week特征表示一個人每周的工作時間。這個特征可用來計算平均工作時間、標準差、大值和小值。pandas提 供了常見統計量的計算方法。
adult["Hours-per-week"].describe()結果:
count 32561.000000
mean 40.437456
std 12.347429
min 1.000000
25% 40.000000
50% 40.000000
75% 45.000000
max 99.000000
Name: Hours-per-week, dtype: float64
對于序數特征,Education-Num(受教育年限)
adult["Education-Num"].median()結果為10,或者可以表述為剛好讀完高一。如果沒有受教育年限數據,我們為不同教育階段 指定數字編號,也可以計算均值。
特征也可以是類別型的。例如,一個球可以是網球、棒球、足球或其他種類。類別型特征也可以稱為名義特征(nominal)。對于名義特征而言,幾個值之間要么相同要么不同。
類別型特征二值化后就變成了數值型特征,對于上述球的種類,可以創建三個二值特征:“是網球”“是棒球”和“是足球”。如果是網球的話,特征向量就是[1, 0, 0], 棒球[0, 1, 0],足球[0, 0, 1]。這些特征都只有兩個取值,很多算法都可以把它們作為連續型特征使用。二值化的好處是,便于直接進行數字上的比較(例如計算個體之間的距離)。
再舉一個類別形的例子,,例如Work-Class(工作),其中它的有些值可以進行量級上的比較,比如不同的就業狀況影響收入(例如有工作的人比沒有工作的人收入高)
adult["Work-Class"].unique()array([’ State-gov’, ’ Self-emp-not-inc’, ’ Private’, ’ Federal-gov’,
’ Local-gov’, ’ ?’, ’ Self-emp-inc’, ’ Without-pay’, ’ Never-worked’], dtype=object)
數據集中有部分數據缺失,但不會影響計算。
同理,數值型特征也可以通過離散化過程轉換為類別型特征,例如,高于 1.7m的人,我們稱其為高個子,低于1.7m的叫作矮個子。這樣就得到了類別型特征(雖然是序數 值類型)。在這個過程中確實丟失了一些信息。例如,有兩個人,身高分別為1.69m和1.71m,這 兩個個子差不多的將被分到兩個截然不同的類別里。而身高僅為1.2m的將會被認為與身高1.69m 的“差不多高”!細節的丟失是離散化不好的一面,也是建模時需要考慮解決的問題。
對于Adult數據集,我們可以創建LongHours(時長)特征,用它來表示一個人每周工作時 長是否多于40小時。這樣就把連續值(Hours-per-week,每周工作時長)轉換為類別型特征。
adult["LongHours"] = adult["Hours-per-week"] > 40創建好的特征
建模過程中,需要對真實世界中的對象進行簡化,這會導致信息的丟失,這也就是為什么沒有一套能夠用于任何數據集的通用的數據挖掘方法。數據挖掘的行家里手需要擁有數據來源領域的知識,沒有的話,要積極去掌握。他們弄清楚問題是什么,了解有哪些可用數據后,在此基礎上,才能創建解決問題所需的模型。
例如,身高這個特征描述的是一個人外表的某個方面,但是不能說明這個人學習成績如何, 所以預測學習成績時,我們沒必要去測量每個人的身高。
這正是數據挖掘為什么變得更像是一門藝術而不是科學的原因所在。抽取好的特征難度不小,該課題具有重要研究意義,因此它獲得了研究人員的持續關注。選擇好的分類算法也可以提升數據挖掘應用的效果,但好還是通過選用好的特征來達到同樣的目的。
通常特征數量很多,但我們只想選用其中一小部分。有如下幾個原因。
降低復雜度: 隨著特征數量的增加,很多數據挖掘算法需要更多的時間和資源。減少特征數量,是提高算法運行速度,減少資源使用的好方法。
降低噪聲 : 增加額外特征并不總會提升算法的表現。額外特征可能擾亂算法的正常工作, 這些額外特征間的相關性和模式沒有實際應用價值(這種情況在小數據集上很常見)。只 選擇合適的特征有助于減少出現沒有實際意義的相關性的幾率。
增加模型可讀性:根據成千上萬個特征創建的模型來解答一個問題,對計算機來說很容易,但模型對我們自己來說就晦澀無比。因此,使用更少的特征,創建我們自己可以理解的模型,就很有必要。
scikit-learn中的VarianceThreshold轉換器可用來刪除特征值的方差達不到低標準 的特征。下面通過代碼來講下它的用法。
import numpy as np X = np.arange(30).reshape((10, 3))上述矩陣包含0到29,共30個數字,分為3列10行。可以把它看成一個有10個個體、3個特征的數據集。
接著,把所有第二列的數值都改為1
X[:,1] = 1第一、三列特征值方差很大,而第二列方差為0。
這時再來創建VarianceThreshold轉換器,用它處理數據集。
from sklearn.feature_selection import VarianceThresholdvt = VarianceThreshold() Xt = vt.fit_transform(X)輸出Xt后,我們發現第二列消失了。
array([[ 0, 2],
[ 3, 5],
[ 6, 8],
[ 9, 11],
[12, 14],
[15, 17],
[18, 20],
[21, 23],
[24, 26],
[27, 29]])
輸出每一列的方差:
下面輸出結果表明第一、三列包含有價值信息,第二列方差為0,不包含具有區別意義的信息。
array([ 74.25, 0. , 74.25])
無論什么時候,拿到數據后,先做下類似簡單、直接的分析,對數據集的特點做到心中有數。 方差為0的特征不但對數據挖掘沒有絲毫用處,相反還會拖慢算法的運行速度。
選擇最佳特征
在特征逐漸變多的情況下,特征的組合復雜度呈指數增加。尋找最佳組合的時間復雜度也是呈指數增加。
其中一個變通方法是不要找表現好的子集,而只是去找表現好的單個特征(單變量),依據是它們各自所能達到的精確度。分類任務通常是這么做的,我們一般只要測量變量和目標類別之間的某種相關性就行。
scikit-learn提供了幾個用于選擇單變量特征的轉換器,其中SelectKBest返回k個佳特征,SelectPercentile返回表現佳的前r%個特征。這兩個轉換器都提供計算特征表現的一系列方法。
單個特征和某一類別之間相關性的計算方法有很多。常用的有卡方檢驗(χ2)。其他方法還有互信息和信息熵。
我們可以測試單個特征在Adult數據集上的表現。首先,選取下述特征,從pandas數據框中抽 取一部分數據。
接著,判斷Earnings-Raw(稅前收入)是否達到五萬美元,創建目標類別列表。如果達到, 類別為True,否則,類別為False。
X = adult[["Age", "Education-Num", "Capital-gain", "Capital-loss", "Hours-per-week"]].values y = (adult["Earnings-Raw"] == ' >50K').values再使用SelectKBest轉換器類,用卡方函數打分,初始化轉換器。
from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 transformer = SelectKBest(score_func=chi2, k=3)調用fit_transform方法,對相同的數據集進行預處理和轉換。結果為分類效果較好的三個特征。代碼如下
Xt_chi2 = transformer.fit_transform(X, y) print(transformer.scores_)生成的矩陣只包含三個特征。我們還可以得到每一列的相關性,這樣就可以知道都使用了哪些特征。
結果:[8.60061182e+03 2.40142178e+03 8.21924671e+07 1.37214589e+06
6.47640900e+03]
相關性好的分別是第一、三、四列,分別對應著Age(年齡)、Capital-Gain(資本收 益)和Capital-Loss(資本損失)三個特征。從單變量特征選取角度來說,這些就是佳特征。
還可以用其他方法計算相關性,比如皮爾遜(Pearson)相關系數。用于科學計算的SciPy 庫(scikit-learn在它基礎上開發,安裝scikit-learn時默認安裝了)實現了該方法。
pearsonr函數的接口幾乎與scikit-learn單變量轉換器接口一致,該函數接收兩個數組 (當前例子中為x和y)作為參數,返回兩個數組:每個特征的皮爾遜相關系數和p值(p-value)。 前面用到的chi2函數使用的接口符合要求,因此我們直接把它傳入到SelectKBest函數中。
SciPy的pearsonr函數參數為兩個數組,但要注意的是第一個參數x為一維數組。我們來實現一個包裝器函數,這樣就能像前面那樣處理多維數組。
from scipy.stats import pearsonrdef multivariate_pearsonr(X, y):# 創建scores和pvalues數組,遍歷數據集的每一列scores, pvalues = [], []for column in range(X.shape[1]):# 只計算該列的皮爾遜相關系數和p值,并將其存儲到相應數組中cur_score, cur_p = pearsonr(X[:,column], y)scores.append(abs(cur_score))pvalues.append(cur_p)# 后返回包含皮爾遜相關系數和p值的元組return (np.array(scores), np.array(pvalues))p值為-1到1之間的任意值。值為1,表示兩個變量之間絕對正相關,值為-1, 絕對負相關,即一個變量值越大,另一個變量值就越小,反之亦然。這樣的特征 確實能反應兩個變量之間的關系,但是根據大小進行排序,這些值因為是負數而 排在后面,可能會被舍棄不用。因此,我們對p值取絕對值后,再保存到scores 數組中,而不是使用原始值。
現在可以使用轉換器,根據皮爾遜相關系數對特征進行排序。
transformer = SelectKBest(score_func=multivariate_pearsonr, k=3) Xt_pearson = transformer.fit_transform(X, y) print(transformer.scores_)[ 0.2340371 0.33515395 0.22332882 0.15052631 0.22968907]
返回的結果和卡方檢驗不一樣,可以說,對于最好的特征選擇,實際上沒有統一的度量標準。
我們最后看一下在該任務下,哪個特征選擇的效果更好:
from sklearn.tree import DecisionTreeClassifier from sklearn.cross_validation import cross_val_score clf = DecisionTreeClassifier(random_state=14) scores_chi2 = cross_val_score(clf, Xt_chi2, y, scoring='accuracy') scores_pearson = cross_val_score(clf, Xt_pearson, y, scoring='accuracy')print("Chi2 performance: {0:.3f}".format(scores_chi2.mean())) print("Pearson performance: {0:.3f}".format(scores_pearson.mean()))chi2方法的平均正確率為0.83,而皮爾遜相關系數正確率為0.77。用卡方檢驗得到的特征組 合效果更好!
我們只用三個特征對于預測收入就能達到83%的正確率,可以說是挺厲害的了。
參考資料
《Python數據挖掘入門與實踐》
總結
- 上一篇: 用亲和性分析方法推荐电影
- 下一篇: 创建自己的特征和转换器