誰動了個人特徵?——sklearn特徵轉換行爲全記錄

目錄

1 爲何要記錄特徵轉換行爲?
2 有哪些特徵轉換的方式?
3 特徵轉換的組合
4 sklearn源碼分析
  4.1 一對一映射
  4.2 一對多映射
  4.3 多對多映射
5 實踐
6 總結
7 參考資料html


1 爲何要記錄特徵轉換行爲?

  使用機器學習算法和模型進行數據挖掘,有時不免事與願違:咱們依仗對業務的理解,對數據的分析,以及工做經驗提出了一些特徵,可是在模型訓練完成後,某些特徵可能「身微言輕」——咱們認爲相關性高的特徵並不重要,這時咱們便要反思這樣的特徵提出是否合理;某些特徵甚至「南轅北轍」——咱們認爲正相關的特徵結果變成了負相關,形成這種狀況頗有多是抽樣與總體不相符,模型過於複雜,致使了過擬合。然而,咱們怎麼判斷先前的假設和最後的結果之間的差別呢?node

  線性模型一般有含有屬性coef_,當係數值大於0時爲正相關,當係數值小於0時爲負相關;另一些模型含有屬性feature_importances_,顧名思義,表示特徵的重要性。根據以上兩個屬性,即可以與先前假設中的特徵的相關性(或重要性)進行對比了。可是,理想是豐滿的,現實是骨感的。通過複雜的特徵轉換以後,特徵矩陣X已再也不是原來的樣子:啞變量使特徵變多了,特徵選擇使特徵變少了,降維使特徵映射到另外一個維度中。python

  累覺不愛了嗎?若是,咱們可以將最後的特徵與原特徵對應起來,那麼分析特徵的係數和重要性又有了意義了。因此,在訓練過程(或者轉換過程)中,記錄下全部特徵轉換行爲是一個有意義的工做。惋惜,sklearn暫時並無提供這樣的功能。在這篇博文中,咱們嘗試對一些常見的轉換功能進行行爲記錄,讀者能夠在此基礎進行進一步的拓展。git


 

2 有哪些特徵轉換的方式?

  《使用sklearn作單機特徵工程》一文歸納了若干常見的轉換功能:github

類名 功能 說明
StandardScaler 數據預處理(無量綱化) 標準化,基於特徵矩陣的列,將特徵值轉換至服從標準正態分佈
MinMaxScaler 數據預處理(無量綱化) 區間縮放,基於最大最小值,將特徵值轉換到[0, 1]區間上
Normalizer 數據預處理(歸一化) 基於特徵矩陣的行,將樣本向量轉換爲「單位向量」
Binarizer 數據預處理(二值化) 基於給定閾值,將定量特徵按閾值劃分
OneHotEncoder 數據預處理(啞編碼) 將定性數據編碼爲定量數據
Imputer 數據預處理(缺失值計算) 計算缺失值,缺失值可填充爲均值等
PolynomialFeatures 數據預處理(多項式數據轉換) 多項式數據轉換
FunctionTransformer 數據預處理(自定義單元數據轉換) 使用單變元的函數來轉換數據
VarianceThreshold 特徵選擇(Filter) 方差選擇法
SelectKBest 特徵選擇(Filter) 可選關聯繫數、卡方校驗、最大信息係數做爲得分計算的方法
RFE 特徵選擇(Wrapper) 遞歸地訓練基模型,將權值係數較小的特徵從特徵集合中消除
SelectFromModel 特徵選擇(Embedded) 訓練基模型,選擇權值係數較高的特徵
PCA 降維(無監督) 主成分分析法
LDA 降維(有監督) 線性判別分析法

  按照特徵數量是否發生變化,這些轉換類可分爲:算法

  • 無變化:StandardScaler,MinMaxScaler,Normalizer,Binarizer,Imputer,FunctionTransformer*
  • 有變化:OneHotEncoder,PolynomialFeatures,VarianceThreshold,SelectKBest,RFE,SelectFromModel,PCA,LDA
FunctionTransformer*:自定義的轉換函數一般不會使特徵數量發生變化

  對於不形成特徵數量變化的轉換類,咱們只須要保持特徵不變便可。在此,咱們主要研究那些有變化的轉換類,其餘轉換類都默認爲無變化。按照映射的形式,可將以上有變化的轉換類可分爲:數組

  • 一對一:VarianceThreshold,SelectKBest,RFE,SelectFromModel
  • 一對多:OneHotEncoder
  • 多對多:PolynomialFeatures,PCA,LDA

  原特徵與新特徵爲一對一映射一般發生在特徵選擇時,若原特徵被選擇則直接變成新特徵,不然拋棄。啞編碼爲典型的一對多映射,須要啞編碼的原特徵將會轉換爲多個新特徵。多對多的映射中PolynomialFeatures並不要求每個新特徵都與原特徵創建映射關係,例如階爲2的多項式轉換,第一個新特徵只由第一個原特徵生成(平方)。降維的本質在於將原特徵矩陣X映射到維度更低的空間中,使用的技術一般是矩陣乘法,因此它既要求每個原特徵映射到全部新特徵,同時也要求每個新特徵被全部原特徵映射。網絡


 

3 特徵轉換的組合

  在《使用sklearn優雅地進行數據挖掘》一文中,咱們看到一個基本的數據挖掘場景:app

  特徵轉換行爲一般是流水線型和並行型結合的。因此,咱們考慮從新設計流水線處理類Pipeline和並行處理類FeatureUnion,使其可以根據不一樣的特徵轉換類,記錄下轉換行爲「日誌」。「日誌」的表示形式也是重要的,由上圖可知,集成後的特徵轉換過程呈現無環網狀,故使用網絡來描述「日誌」是合適的。在網絡中,節點表示特徵,有向連線表示特徵轉換。dom

  爲此,咱們新增兩個類型Feature和Transfrom來構造網絡結構,Feature類型表示網絡中的節點,Transform表示網絡中的有向邊。python的networkx庫能夠很好地表述網絡和操做網絡,我這是要從新造輪子嗎?其實並非,如今考慮表明新特徵的節點怎麼命名的問題,顯然,不能與網絡中任意節點同名,不然會發生混淆。然而,因爲sklearn的訓練過程存在並行過程(線程),直接使用network來構造網絡的話,將難以處理節點重複命名的問題。因此,我才新增兩個新的類型來描述網絡結構,這時網絡中的節點名是能夠重複的。最後,對這網絡進行廣度遍歷,生成基於networkx庫的網絡,由於這個過程是串行的,故可使用「當前節點數」做爲新增節點的序號了。這兩個類的代碼(feature.py)設計以下:

 1 import numpy as np
 2 
 3 class Transform(object):
 4     def __init__(self, label, feature):
 5         super(Transform, self).__init__()
 6         #邊標籤名,使用networkx等庫畫圖時將用到
 7         self.label = label
 8         #該邊指向的節點
 9         self.feature = feature
10 
11 class Feature(object):
12     def __init__(self, name):
13         super(Feature, self).__init__()
14         #節點名稱,該名稱在網絡中不惟一,在某些映射中,該名稱須要直接傳給新特徵
15         self.name = name
16         #節點標籤名,該名稱在網絡中惟一,使用networkx等庫畫圖時將用到
17         self.label = '%s[%d]' % (self.name, id(self))
18         #從本節點發出的有向邊列表
19         self.transformList = np.array([])
20 
21     #創建從self到feature的有向邊
22     def transform(self, label, feature):
23         self.transformList = np.append(self.transformList, Transform(label, feature))
24 
25     #深度遍歷輸出以本節點爲源節點的網絡
26     def printTree(self):
27         print self.label
28         for transform in self.transformList:
29             feature = transform.feature
30             print '--%s-->' % transform.label,
31             feature.printTree()
32 
33     def __str__(self):
34         return self.label

 

4 sklearn源碼分析

  咱們能夠統一地記錄不改變特徵數量的轉換行爲:在「日誌」網絡中,從表明原特徵的節點,引申出連線連上惟一的表明新特徵的節點。然而,對於改變特徵數量的轉換行爲來講,須要針對每一個轉換類編寫不一樣的「日誌」記錄(網絡生成)代碼。爲不改變特徵數量的轉換行爲設計代碼(default.py)以下:

 1 import numpy as np
 2 from feature import Feature
 3 
 4 def doWithDefault(model, featureList):
 5     leaves = np.array([])
 6 
 7     n_features = len(featureList)
 8     
 9     #爲每個輸入的原節點,新建一個新節點,並創建映射
10     for i in range(n_features):
11         feature = featureList[i]
12         newFeature = Feature(feature.name)
13         feature.transform(model.__class__.__name__, newFeature)
14         leaves = np.append(leaves, newFeature)
15 
16     #返回新節點列表,之因此該變量取名叫leaves,是由於其是網絡的邊緣節點
17     return leaves

4.1 一對一映射

  映射形式爲一對一時,轉換類一般爲特徵選擇類。在這種映射下,原特徵要麼只轉化爲一個新特徵,要麼不轉化。經過分析sklearn源碼不難發現,特徵選擇類都混入了特質sklearn.feature_selection.base.SelectorMixin,所以這些類都有方法get_support來獲取哪些特徵轉換信息:

  因此,在設計「日誌」記錄模塊時,判斷轉換類是否混入了該特徵,如果則直接調用get_support方法來獲得被篩選的特徵的掩碼或者下標,如此咱們即可從被篩選的特徵引申出連線連上新特徵。爲此,咱們設計代碼(one2one.py)以下:

 1 import numpy as np
 2 from sklearn.feature_selection.base import SelectorMixin
 3 from feature import Feature
 4 
 5 def doWithSelector(model, featureList):
 6     assert(isinstance(model, SelectorMixin))
 7 
 8     leaves = np.array([])
 9 
10     n_features = len(featureList)
11     
12     #新節點的掩碼
13     mask_features = model.get_support()
14 
15     for i in range(n_features):
16         feature = featureList[i]
17         #原節點被選擇,生成新節點,並創建映射
18         if mask_features[i]:
19             newFeature = Feature(feature.name)
20             feature.transform(model.__class__.__name__, newFeature)
21             leaves = np.append(leaves, newFeature)
22         #原節點被拋棄,生成一個名爲Abandomed的新節點,創建映射,可是這個特徵不加入下一步繼續生長的節點列表
23         else:
24             newFeature = Feature('Abandomed')
25             feature.transform(model.__class__.__name__, newFeature)
26 
27     return leaves

4.2 一對多映射

  OneHotEncoder是典型的一對多映射轉換類,其提供了兩個屬性結合兩個參數來表示轉換信息:

  • n_values:定性特徵的值數量,若爲auto則直接從訓練集中獲取,若爲整數則表示全部定性特徵的值數量+1,若爲數組則分別表示每一個定性特徵的數量+1
  • categorical_features:定性特徵的掩碼或下標
  • active_features_:有效值(在n_values爲auto時有用),假設A屬性取值範圍爲(1,2,3),可是實際上訓練樣本中只有(1,2),假設B屬性取值範圍爲(2,3,4),訓練樣本中只有(2,4),那麼有效值爲(1,2,5,7)。是否是感到奇怪了,爲何有效值不是(1,2,2,4)?OneHotEncoder在這裏作了巧妙的設計:有效值被轉換成了一個遞增的序列,這樣方便於配合屬性n_features快速地算出每一個原特徵轉換成了哪些新特徵,轉換依據的真實有效值是什麼。
  • feature_indices_:每一個定性特徵的有效值範圍,例如第i個定性特徵,其有效值範圍爲feature_indices_[i]至feature_indices_[i+1],sklearn官方文檔在此描述有誤,該數組的長度應爲n_features+1。在上例中,feature_indices_等於(0,3,8)。故下標爲0的定性特徵,其有效值範圍爲大於0小於3,則有效值爲1和2;下標爲1的定性特徵,其有效值範圍爲大於3小於8,則有效值爲5和7。下標爲0的定性特徵,其兩個真實有效值爲1-0=1和2-0=2;下標爲1的定性特徵,其兩個真實有效值爲5-3=2和7-3=4。這樣一來就能夠獲得(1,2,2,4)的真實有效值了。

  綜上,咱們設計處理OneHotEncoder類的代碼(one2many.py)以下:

 1 import numpy as np
 2 from sklearn.preprocessing import OneHotEncoder
 3 from feature import Feature
 4 
 5 def doWithOneHotEncoder(model, featureList):
 6     assert(isinstance(model, OneHotEncoder))
 7     assert(hasattr(model, 'feature_indices_'))
 8 
 9     leaves = np.array([])
10 
11     n_features = len(featureList)
12     
13     #定性特徵的掩碼
14     if model.categorical_features == 'all':
15         mask_features = np.ones(n_features)
16     else:
17         mask_features = np.zeros(n_features)
18         mask_features[self.categorical_features] = 1
19 
20     #定性特徵的數量
21     n_qualitativeFeatures = len(model.feature_indices_) - 1
22     #若是定性特徵的取值個數是自動的,即從訓練數據中生成
23     if model.n_values == 'auto':
24         #定性特徵的有效取值列表
25         n_activeFeatures = len(model.active_features_)
26     #變量j爲定性特徵的下標,變量k爲有效值的下標
27     j = k = 0
28     for i in range(n_features):
29         feature = featureList[i]
30         #若是是定性特徵
31         if mask_features[i]:
32             if model.n_values == 'auto':
33                 #爲屬於第j個定性特徵的每一個有效值生成一個新節點,創建映射關係
34                 while k < n_activeFeatures and model.active_features_[k] < model.feature_indices_[j+1]:
35                     newFeature = Feature(feature.name)
36                     feature.transform('%s[%d]' % (model.__class__.__name__, model.active_features_[k] - model.feature_indices_[j]), newFeature)
37                     leaves = np.append(leaves, newFeature)
38                     k += 1
39             else:
40                 #爲屬於第j個定性特徵的每一個有效值生成一個新節點,創建映射關係
41                 for k in range(model.feature_indices_[j]+1, model.feature_indices_[j+1]):
42                     newFeature = Feature(feature.name)
43                     feature.transform('%s[%d]' % (model.__class__.__name__, k - model.feature_indices_[j]), newFeature)
44                     leaves = np.append(leaves, newFeature)
45             j += 1
46         #若是不是定性特徵,則直接根據原節點生成新節點
47         else:
48             newFeature = Feature(feature.name)
49             feature.transform('%s[r]' % model.__class__.__name__, newFeature)
50             leaves = append(leaves, newFeatures)
51 
52     return leaves

4.3 多對多映射

  PCA類是典型的多對多映射的轉換類,其提供了參數n_components_來表示轉換後新特徵的個數。以前說過降維的轉換類,其既要求每個原特徵映射到全部新特徵,也要求每個新特徵被全部原特徵映射。故,咱們設計處理PCA類的代碼(many2many.py)以下:

 1 import numpy as np
 2 from sklearn.decomposition import PCA
 3 from feature import Feature
 4 
 5 def doWithPCA(model, featureList):
 6     leaves = np.array([])
 7 
 8     n_features = len(featureList)
 9     
10     #按照主成分數生成新節點
11     for i in range(model.n_components_):
12         newFeature = Feature(model.__class__.__name__)
13         leaves = np.append(leaves, newFeature)
14 
15     #爲每個原節點與每個新節點創建映射
16     for i in range(n_features):
17         feature = featureList[i]
18         for j in range(model.n_components_):
19             newFeature = leaves[j]
20             feature.transform(model.__class__.__name__, newFeature)
21 
22     return leaves

5 實踐

  到此,咱們能夠專一改進流水線處理和並行處理的模塊了。爲了避免破壞Pipeline類和FeatureUnion類的核心功能,咱們分別派生出兩個類PipelineExt和FeatureUnionExt。其次,爲這兩個類增長私有方法getFeatureList,這個方法有隻有一個參數featureList表示輸入流水線處理或並行處理的特徵列表(元素爲feature.Feature類的對象),輸出通過流水線處理或並行處理後的特徵列表。設計內部方法_doWithModel,其被getFeatureList方法調用,其提供了一個公共的入口,將根據流水線上或者並行中的轉換類的不一樣,具體調用不一樣的處理方法(這些不一樣的處理方法在one2one.py,one2many.py,many2many.py中定義)。在者,咱們還須要一個initRoot方法來初始化網絡結構,返回一個根節點。最後,咱們嘗試用networkx庫讀取自定義的網絡結構,基於matplotlib的對網絡進行圖形化顯示。以上部分的代碼(ple.py)以下:

  1 from sklearn.feature_selection.base import SelectorMixin
  2 from sklearn.preprocessing import OneHotEncoder
  3 from sklearn.decomposition import PCA
  4 from sklearn.pipeline import Pipeline, FeatureUnion, _fit_one_transformer, _fit_transform_one, _transform_one 
  5 from sklearn.externals.joblib import Parallel, delayed
  6 from scipy import sparse
  7 import numpy as np
  8 import networkx as nx
  9 from matplotlib import pyplot as plt
 10 from default import doWithDefault
 11 from one2one import doWithSelector
 12 from one2many import doWithOneHotEncoder
 13 from many2many import doWithPCA
 14 from feature import Feature
 15 
 16 #派生Pipeline類
 17 class PipelineExt(Pipeline):
 18     def _pre_get_featues(self, featureList):
 19         leaves = featureList
 20         for name, transform in self.steps[:-1]:
 21             leaves = _doWithModel(transform, leaves)
 22         return leaves
 23 
 24     #定義getFeatureList方法
 25     def getFeatureList(self, featureList):
 26         leaves = self._pre_get_featues(featureList)
 27         model = self.steps[-1][-1]
 28         if hasattr(model, 'fit_transform') or hasattr(model, 'transform'):
 29             leaves = _doWithModel(model, leaves)
 30         return leaves
 31 
 32 #派生FeatureUnion類,該類不只記錄了轉換行爲,同時也支持部分數據處理
 33 class FeatureUnionExt(FeatureUnion):
 34     def __init__(self, transformer_list, idx_list, n_jobs=1, transformer_weights=None):
 35         self.idx_list = idx_list
 36         FeatureUnion.__init__(self, transformer_list=map(lambda trans:(trans[0], trans[1]), transformer_list), n_jobs=n_jobs, transformer_weights=transformer_weights)
 37 
 38     def fit(self, X, y=None):
 39         transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
 40         transformers = Parallel(n_jobs=self.n_jobs)(
 41             delayed(_fit_one_transformer)(trans, X[:,idx], y)
 42             for name, trans, idx in transformer_idx_list)
 43         self._update_transformer_list(transformers)
 44         return self
 45 
 46     def fit_transform(self, X, y=None, **fit_params):
 47         transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
 48         result = Parallel(n_jobs=self.n_jobs)(
 49             delayed(_fit_transform_one)(trans, name, X[:,idx], y,
 50                                         self.transformer_weights, **fit_params)
 51             for name, trans, idx in transformer_idx_list)
 52 
 53         Xs, transformers = zip(*result)
 54         self._update_transformer_list(transformers)
 55         if any(sparse.issparse(f) for f in Xs):
 56             Xs = sparse.hstack(Xs).tocsr()
 57         else:
 58             Xs = np.hstack(Xs)
 59         return Xs
 60 
 61     def transform(self, X):
 62         transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
 63         Xs = Parallel(n_jobs=self.n_jobs)(
 64             delayed(_transform_one)(trans, name, X[:,idx], self.transformer_weights)
 65             for name, trans, idx in transformer_idx_list)
 66         if any(sparse.issparse(f) for f in Xs):
 67             Xs = sparse.hstack(Xs).tocsr()
 68         else:
 69             Xs = np.hstack(Xs)
 70         return Xs
 71 
 72     #定義getFeatureList方法
 73     def getFeatureList(self, featureList):
 74         transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
 75         leaves = np.array(Parallel(n_jobs=self.n_jobs)(
 76             delayed(_doWithModel)(trans, featureList[idx])
 77             for name, trans, idx in transformer_idx_list))
 78         leaves = np.hstack(leaves)
 79         return leaves
 80 
 81 #定義爲每一個模型進行轉換記錄的總入口方法,該方法將根據不一樣的轉換類調用不一樣的處理方法
 82 def _doWithModel(model, featureList):
 83     if isinstance(model, SelectorMixin):
 84         return doWithSelector(model, featureList)
 85     elif isinstance(model, OneHotEncoder):
 86         return doWithOneHotEncoder(model, featureList)
 87     elif isinstance(model, PCA):
 88         return doWithPCA(model, featureList)
 89     elif isinstance(model, FeatureUnionExt) or isinstance(model, PipelineExt):
 90         return model.getFeatureList(featureList)
 91     else:
 92         return doWithDefault(model, featureList)
 93 
 94 #初始化網絡的根節點,輸入參數爲原始特徵的名稱
 95 def initRoot(featureNameList):
 96     root = Feature('root')
 97     for featureName in featureNameList:
 98         newFeature = Feature(featureName)
 99         root.transform('init', newFeature)
100     return root

  如今,咱們須要驗證一下成果了,不妨繼續使用博文《使用sklearn優雅地進行數據挖掘》中提供的場景來進行測試:

 1 import numpy as np
 2 from sklearn.datasets import load_iris
 3 from sklearn.preprocessing import Imputer
 4 from sklearn.preprocessing import OneHotEncoder
 5 from sklearn.preprocessing import FunctionTransformer
 6 from sklearn.preprocessing import Binarizer
 7 from sklearn.preprocessing import MinMaxScaler
 8 from sklearn.feature_selection import SelectKBest
 9 from sklearn.feature_selection import chi2
10 from sklearn.decomposition import PCA
11 from sklearn.linear_model import LogisticRegression
12 from sklearn.pipeline import Pipeline, FeatureUnion
13 from ple import PipelineExt, FeatureUnionExt, initRoot
14 
15 def datamining(iris, featureList):
16     step1 = ('Imputer', Imputer())
17     step2_1 = ('OneHotEncoder', OneHotEncoder(sparse=False))
18     step2_2 = ('ToLog', FunctionTransformer(np.log1p))
19     step2_3 = ('ToBinary', Binarizer())
20     step2 = ('FeatureUnionExt', FeatureUnionExt(transformer_list=[step2_1, step2_2, step2_3], idx_list=[[0], [1, 2, 3], [4]]))
21     step3 = ('MinMaxScaler', MinMaxScaler())
22     step4 = ('SelectKBest', SelectKBest(chi2, k=3))
23     step5 = ('PCA', PCA(n_components=2))
24     step6 = ('LogisticRegression', LogisticRegression(penalty='l2'))
25     pipeline = PipelineExt(steps=[step1, step2, step3, step4, step5, step6])
26     pipeline.fit(iris.data, iris.target)
27     #最終的特徵列表
28     leaves = pipeline.getFeatureList(featureList)
29     #爲最終的特徵輸出對應的係數
30     for i in range(len(leaves)):
31         print leaves[i], pipeline.steps[-1][-1].coef_[i]
32 
33 def main():
34     iris = load_iris()
35     iris.data = np.hstack((np.random.choice([0, 1, 2], size=iris.data.shape[0]+1).reshape(-1,1), np.vstack((iris.data, np.full(4, np.nan).reshape(1,-1)))))
36     iris.target = np.hstack((iris.target, np.array([np.median(iris.target)])))
37     root = initRoot(['color', 'Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width'])
38     featureList = np.array([transform.feature for transform in root.transformList])
39 
40     datamining(iris, featureList)
41 
42     root.printTree()
43 
44 if __name__ == '__main__':
45     main()

  運行程序,最終的特徵及對應的係數輸出以下:

  輸出網絡結構的深度遍歷(部分截圖):

  爲了更好的展現轉換行爲構成的網絡,咱們還能夠基於networkx構建有向圖,經過matplotlib進行展現(ple.py):

 1 #遞歸的方式進行深度遍歷,生成基於networkx的有向圖
 2 def _draw(G, root, nodeLabelDict, edgeLabelDict):
 3     nodeLabelDict[root.label] = root.name
 4     for transform in root.transformList:
 5         G.add_edge(root.label, transform.feature.label)
 6         edgeLabelDict[(root.label, transform.feature.label)] = transform.label
 7         _draw(G, transform.feature, nodeLabelDict, edgeLabelDict)
 8 
 9 #判斷是否圖是否存在環
10 def _isCyclic(root, walked):
11     if root in walked:
12         return True
13     else:
14         walked.add(root)
15         for transform in root.transformList:
16             ret = _isCyclic(transform.feature, walked)
17             if ret:
18                 return True
19         walked.remove(root)
20         return False
21 
22 #廣度遍歷生成瀑布式佈局
23 def fall_layout(root, x_space=1, y_space=1):
24     layout = {}
25     if _isCyclic(root, set()):
26         raise Exception('Graph is cyclic')
27     
28     queue = [None, root]
29     nodeDict = {}
30     levelDict = {}
31     level = 0
32     while len(queue) > 0:
33         head = queue.pop()
34         if head is None:
35             if len(queue) > 0:
36                 level += 1
37                 queue.insert(0, None)
38         else:
39             if head in nodeDict:
40                 levelDict[nodeDict[head]].remove(head)
41             nodeDict[head] = level
42             levelDict[level] = levelDict.get(level, []) + [head]
43             for transform in head.transformList:
44                 queue.insert(0, transform.feature)
45 
46     for level in levelDict.keys():
47         nodeList = levelDict[level]
48         n_nodes = len(nodeList)
49         offset = - n_nodes / 2
50         for i in range(n_nodes):
51             layout[nodeList[i].label] = (level * x_space, (i + offset) * y_space)
52 
53     return layout
54 
55 def draw(root):
56     G = nx.DiGraph()
57     nodeLabelDict = {}
58     edgeLabelDict = {}
59 
60     _draw(G, root, nodeLabelDict, edgeLabelDict)
61     #設定網絡佈局方式爲瀑布式
62     pos = fall_layout(root)
63 
64     nx.draw_networkx_nodes(G,pos,node_size=100, node_color="white")
65     nx.draw_networkx_edges(G,pos, width=1,alpha=0.5,edge_color='black')
66     #設置網絡中節點的標籤內容及格式
67     nx.draw_networkx_labels(G,pos,labels=nodeLabelDict, font_size=10,font_family='sans-serif')
68     #設置網絡中邊的標籤內容及格式
69     nx.draw_networkx_edge_labels(G, pos, edgeLabelDict)
70 
71     plt.show()

  以圖形界面展現網絡的結構:

6 總結

  聰明的讀者你確定發現了,記錄下特徵轉換行爲的最好時機實際上是轉換的同時。惋惜的是,sklearn目前並不支持這樣的功能。在本文中,我將這一功能集中到流水線處理和並行處理的模塊當中,只能算是一個臨時的手段,但聊勝於無吧。另外,本文也是拋磚引玉,還有其餘的轉換類,在原特徵與新特徵之間的映射關係上,百家爭鳴。因此,我在Github上新建了個庫,包含本文實例中全部的轉換類處理的代碼,在以後,我會慢慢地填這個坑,直到世界的盡頭,抑或sklearn加入該功能。

7 參考資料

  1. 《使用sklearn優雅地進行數據挖掘》
  2. 《使用sklearn作單機特徵工程》
  3. sklearn.preprocessing.OneHotEncoder
相關文章
相關標籤/搜索