?? 說起特征工程,都說是機器學習建模中最為重要而且費時的一項工作,而且它涉及的知識點會非常地多,經(jīng)驗老道的老司機自然是輕車熟路了,但對于剛剛?cè)腴T的新手司機,學習到的知識點都是東一點西一點的,不夠系統(tǒng)化,本篇文章是在閱讀了一本評分極高的特征工程書籍 ?? 《特征工程入門與實踐》后的一篇筆記文,記錄下相對比較系統(tǒng)的知識點以及可運行復(fù)現(xiàn)的代碼,希望對各位同行有所幫助哈。

<center> 圖:強力推薦這本書 </center>
?? 目錄
- ?? 特征理解
- ?? 特征增強
- ?? 特征構(gòu)建
- ? 特征選擇
- ?? 特征轉(zhuǎn)換
- ?? 特征學習
大家可以先看下思維導(dǎo)圖:

?? 01 特征理解
在拿到數(shù)據(jù)的時候,我們第一步需要做的是理解它,一般我們可以從下面幾個角度入手:
(注:本節(jié)用到了兩個數(shù)據(jù)集,分別是Salary_Ranges_by_Job_Classification 和 GlobalLandTemperaturesByCity)
1. 區(qū)分結(jié)構(gòu)化數(shù)據(jù)與非結(jié)構(gòu)化數(shù)據(jù)
如一些以表格形式進行存儲的數(shù)據(jù),都是結(jié)構(gòu)化數(shù)據(jù);而非結(jié)構(gòu)化數(shù)據(jù)就是一堆數(shù)據(jù),類似于文本、報文、日志之類的。
2. 區(qū)分定量和定性數(shù)據(jù)
定量數(shù)據(jù):指的是一些數(shù)值,用于衡量某件東西的數(shù)量;
定性數(shù)據(jù):指的是一些類別,用于描述某件東西的性質(zhì)。
其實區(qū)分了定量和定性數(shù)據(jù),還可以繼續(xù)細分下去,分為定類(nominal)、定序(ordinal)、定距(interval)、定比數(shù)據(jù)(ratio),下面我們分別對這4類數(shù)據(jù)進行舉例說明,加深大家對它們的印象。
1)定類(nominal)
也就是分類,比如:血型(A/B/O/AB型)、性別(男/女)、貨幣(人民幣/美元/日元),而且值得注意的是這些分類之間沒有大小可比性。一般畫圖的話就只能看下分布占比,可以用條形圖、餅圖來表示。

2)定序(ordinal)
定序相比于定類,也就是多了一個“可排序”的屬性,也就是說雖然它們是類別變量,但是它們的變量值之間是存在“大小”之分的。比如:期末績點(A、B、C、D、E、F)、問卷答案(非常滿意、滿意、一般、不滿意)??梢暬矫?,和定類一樣,不過就是多了一個 箱體圖 可以用(因為定序變量可以有中位數(shù))。

3)定距(interval)
定距的話,就是變量值之間可以做加減法計算,也就是可以引入均值、方差之類的名詞了,而且能夠畫的圖也多了,包括先前的那些,還包括了直方圖。

4)定比(ratio)
定比相比于定距更加嚴格,不僅僅有定距的所有屬性,同時,有一個 絕對零點 的概念,可以做加減乘除運算,比如說某個商品的價格是另一個的2倍。值得注意的是,溫度一般不歸入定比,而是定距,沒有說20度是10度的兩倍這種說法。

最后把上面的內(nèi)容總結(jié)一下:

3. 關(guān)鍵代碼匯集
以下的代碼只是核心片段,完整代碼可在公眾號(SAMshare)后臺輸入關(guān)鍵字 特征工程 獲取。
1)常見簡易畫圖
# 繪制條形圖
salary_ranges['Grade'].value_counts().sort_values(ascending=False).head(10).plot(kind='bar')
# 繪制餅圖
salary_ranges['Grade'].value_counts().sort_values(ascending=False).head(5).plot(kind='pie')
# 繪制箱體圖
salary_ranges['Union Code'].value_counts().sort_values(ascending=False).head(5).plot(kind='box')
# 繪制直方圖
climate['AverageTemperature'].hist()
# 為每個世紀(Century)繪制平均溫度的直方圖
climate_sub_china['AverageTemperature'].hist(by=climate_sub_china['Century'],
sharex=True,
sharey=True,
figsize=(10, 10),
bins=20)
# 繪制散點圖
x = climate_sub_china['year']
y = climate_sub_china['AverageTemperature']
fig, ax = plt.subplots(figsize=(10,5))
ax.scatter(x, y)
plt.show()
2)檢查缺失情況
# 移除缺失值
climate.dropna(axis=0, inplace=True)
# 檢查缺失個數(shù)
climate.isnull().sum()
3)變量類別轉(zhuǎn)換
# 日期轉(zhuǎn)換, 將dt 轉(zhuǎn)換為日期,取年份, 注意map的用法
climate['dt'] = pd.to_datetime(climate['dt'])
climate['year'] = climate['dt'].map(lambda value: value.year)
# 只看中國
climate_sub_china = climate.loc[climate['Country'] == 'China']
climate_sub_china['Century'] = climate_sub_china['year'].map(lambda x:int(x/100 +1))
climate_sub_china.head()
?? 02 特征增強
這一步其實就是數(shù)據(jù)清洗了,雖然上一步中也有涉及到部分清洗工作(比如清除空值、日期轉(zhuǎn)換之類的),但卻是分散的,這節(jié)重點講講數(shù)據(jù)清洗的一些技巧和實踐代碼,供大家在實際項目中去使用。
Step1: 進行EDA(Exploratory Data Analysis),思路如下:
(1)首先看看目標占比情況(針對二分類問題,也就是0和1的占比情況),直接 value_counts()就可以解決,看看樣本是否失衡。
(2)接著看看有沒有空值,直接統(tǒng)計 isnull().sum() 的個數(shù),不過需要注意的是,可能統(tǒng)計出來沒有缺失,并不是因為真的沒有缺失,而且缺失被人用某個特殊值填充了,一般會用 -9、blank、unknown、0之類的,需要注意??識別,后面需要對缺失進行合理填充。
(2.1)怎么識別缺失值呢?一般可以通過 data.describe() 獲取基本的描述性統(tǒng)計,根據(jù)均值、標準差、極大極小值等指標,結(jié)合變量含義來判斷。

(3)再接著看不同類別之間的特征值分布情況,可通過畫直方圖(數(shù)值型變量)和計算變量值占比分布(類別變量)來觀察。
(4)觀察不同變量之間的相關(guān)性情況,可以通過繪制 相關(guān)矩陣的熱力圖 來觀察大體情況。
Step2: 處理數(shù)據(jù)缺失問題
缺失處理的辦法有好多種,但最為常用的作者講到有兩種:填充和刪除。
而在處理缺失前,我們在上面的小節(jié)中識別出來了部分被人工填充的缺失,需要還原一下:
# 處理被錯誤填充的缺失值0,還原為 空(單獨處理)
pima['serum_insulin'] = pima['serum_insulin'].map(lambda x:x if x !=0 else None)
# 檢查變量缺失情況
pima['serum_insulin'].isnull().sum()
# 批量操作 還原缺失值
columns = ['serum_insulin','bmi','plasma_glucose_concentration','diastolic_blood_pressure','triceps_thickness']
for col in columns:
pima[col].replace([0], [None], inplace=True)
# 檢查變量缺失情況
pima.isnull().sum()
1) 刪除含有缺失值的行
這里的話比較簡單,就是使用 dropna() 來處理即可,同時我們還可以檢查下我們到底刪除了多少數(shù)據(jù)量:round(data.shape[0]-data_dropped.shape[0])/float(data.shape[0]) 就可以統(tǒng)計出來了。當然,刪除之后,我們還需要看看數(shù)據(jù)的分布,對比目標占比、特征分布與先前的是否存在明顯差異,如果是的話,建議不要使用這種辦法。

2) 缺失值合理填充
缺失填充,這里介紹的有均值填充、-9填充、中位數(shù)填充。這里會比較簡單,我們可以通常都是通過 sklearn的 Pipeline以及 Imputer來實現(xiàn),下面是一個簡單的完整Demo:
# 使用sklearn的 Pipeline以及 Imputer來實現(xiàn)缺失值填充
from sklearn.pipeline import Pipeline
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import Imputer
# 調(diào)參候選
knn_params = {'classify__n_neighbors':[1,2,3,4,5,6]}
# 實例化KNN模型
knn = KNeighborsClassifier()
# 管道設(shè)計
mean_impute = Pipeline([('imputer', Imputer(strategy='mean')),
('classify',knn)
])
x = pima.drop('onset_disbetes', axis=1) # 丟棄y
y = pima['onset_disbetes']
# 網(wǎng)格搜索
grid = GridSearchCV(mean_impute, knn_params)
grid.fit(x, y)
# 打印模型效果
print(grid.best_score_, grid.best_params_)
# 0.73177
Step3: 標準化和歸一化
經(jīng)過上面的處理,模型的精度可以達到0.73177,但我們可以繼續(xù)優(yōu)化嗎?那是肯定的。
我們可以先看看所有特征的分布(特征少的時候可以這么看):
pima_imputed_mean.hist(figsize=(15,15))

從上圖中我們可以看出一個問題,那就是每個特征之間的量綱都是不一樣的,這對于knn這種基于距離的模型來說是“致命”的bug,因此我們需要進行標準化和歸一化處理。
我們重點關(guān)注3種方法:
1)Z分數(shù)標準化
最為常用的標準化技術(shù),利用了統(tǒng)計學中的z分數(shù)思想,也就是將數(shù)據(jù)轉(zhuǎn)換為均值為0,標準差為1的分布,其在python中的調(diào)用方法:
# z分數(shù)標準化(單一特征)
from sklearn.preprocessing import StandardScaler
# 實例化方法
scaler = StandardScaler()
glucose_z_score_standarScaler = scaler.fit_transform(pima[['plasma_glucose_concentration']].fillna(-9))
# 可以看看轉(zhuǎn)換之后的均值和標準差是否為0和1
glucose_z_score_standarScaler.mean(), glucose_z_score_standarScaler.std()
# z分數(shù)標準化(全部特征)
from sklearn.preprocessing import StandardScaler
# 實例化方法
scaler = StandardScaler()
pima_imputed_mean_scaled = pd.DataFrame(scaler.fit_transform(pima_imputed_mean), columns=pima_columns)
# 看下標準化之后的分布
pima_imputed_mean_scaled.hist(figsize=(15,15), sharex=True)
# 在Pipeline中使用
model = Pipeline([
('imputer', Imputer()),
('standardize', StandarScaler())
])
2)min-max標準化
min-max標準化和z-score類似,其公式為:(X - Xmin)/(Xmax - Xmin)
在python中的調(diào)用方法:
# min-max標準化
from sklearn.preprocessing import MinMaxScaler
# 實例化方法
min_max = MinMaxScaler()
# 使用min-max標準化
pima_min_maxed = pd.DataFrame(min_max.fit_transform(pima.fillna(-9)), columns=pima_columns)
3)行歸一化
行歸一化針對的是每一行數(shù)據(jù),不同于上面的兩種方法(針對列),對行進行處理是為了保證每行的向量長度一樣(也就是單位范圍,unit norm),有L1、L2范數(shù)。
在python中的調(diào)用方法:
# 行歸一化
from sklearn.preprocessing import Normalizer
# 實例化方法
normalize = Normalizer()
# 使用行歸一化
pima_normalized = pd.DataFrame(normalize.fit_transform(pima.fillna(-9)), columns=pima_columns)
# 查看矩陣的平均范數(shù)(1)
np.sqrt((pima_normalized**2).sum(axis=1)).mean()
?? 03 特征構(gòu)建
如果我們對變量進行處理之后,效果仍不是非常理想,就需要進行特征構(gòu)建了,也就是衍生新變量。
而在這之前,我們需要了解我們的數(shù)據(jù)集,先前兩節(jié)中我們了解到了可以通過 data.info 和 data.describe() 來查看,同時結(jié)合數(shù)據(jù)等級(定類、定序、定距、定比)來理解變量。
?? 基礎(chǔ)操作
本小節(jié)中我們使用一個自定義數(shù)據(jù)集。
# 本次案例使用的數(shù)據(jù)集
import pandas as pd
X = pd.DataFrame({'city':['tokyo',None,'london','seattle','san fancisco','tokyo'],
'boolean':['y','n',None,'n','n','y'],
'ordinal_column':['somewhat like','like','somewhat like','like','somewhat like','dislike'],
'quantitative_column':[1,11,-.5,10,None,20]})
X

首先我們需要對分類變量進行填充操作,類別變量一般用眾數(shù)或者特殊值來填充,回顧之前的內(nèi)容,我們也還是采取Pipeline的方式來進行,因此可以事先基于TransformMixin基類來對填充的方法進行封裝,然后直接在Pipeline中進行調(diào)用,代碼可以參考:
# 填充分類變量(基于TransformerMixin的自定義填充器,用眾數(shù)填充)
from sklearn.base import TransformerMixin
class CustomCategoryzImputer(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
def transform(self, df):
X = df.copy()
for col in self.cols:
X[col].fillna(X[col].value_counts().index[0], inplace=True)
return X
def fit(self, *_):
return self
# 調(diào)用自定義的填充器
cci = CustomCategoryzImputer(cols=['city','boolean'])
cci.fit_transform(X)

又或者利用 scikit-learn 的 Imputer類來實現(xiàn)填充,而這個類有一個 Strategy的方法自然就被繼承過來用了,包含的有mean、median、most_frequent可供選擇。
# 填充分類變量(基于Imputer的自定義填充器,用眾數(shù)填充)
from sklearn.preprocessing import Imputer
class CustomQuantitativeImputer(TransformerMixin):
def __init__(self, cols=None, strategy='mean'):
self.cols = cols
self.strategy = strategy
def transform(self, df):
X = df.copy()
impute = Imputer(strategy=self.strategy)
for col in self.cols:
X[col] = impute.fit_transform(X[[col]])
return X
def fit(self, *_):
return self
# 調(diào)用自定義的填充器
cqi = CustomQuantitativeImputer(cols = ['quantitative_column'], strategy='mean')
cqi.fit_transform(X)

對上面的兩種填充進行流水線封裝:
# 全部填充
from sklearn.pipeline import Pipeline
imputer = Pipeline([('quant',cqi),
('category',cci)
])
imputer.fit_transform(X)
完成了分類變量的填充工作,接下來就需要對分類變量進行編碼了(因為大多數(shù)的機器學習算法都是無法直接對類別變量進行計算的),一般有兩種辦法:獨熱編碼以及標簽編碼。
1)獨熱編碼
獨熱編碼主要是針對定類變量的,也就是不同變量值之間是沒有順序大小關(guān)系的,我們一般可以使用 scikit_learn 里面的 OneHotEncoding來實現(xiàn)的,但我們這里還是使用自定義的方法來加深理解。
# 類別變量的編碼(獨熱編碼)
class CustomDummifier(TransformerMixin):
def __init__(self, cols=None):
self.cols = cols
def transform(self, X):
return pd.get_dummies(X, columns=self.cols)
def fit(self, *_):
return self
# 調(diào)用自定義的填充器
cd = CustomDummifier(cols=['boolean','city'])
cd.fit_transform(X)

2)標簽編碼
標簽編碼是針對定序變量的,也就是有順序大小的類別變量,就好像案例中的變量ordinal_column的值(dislike、somewhat like 和 like 可以分別用0、1、2來表示),同樣的可以寫個自定義的標簽編碼器:
# 類別變量的編碼(標簽編碼)
class CustomEncoder(TransformerMixin):
def __init__(self, col, ordering=None):
self.ordering = ordering
self.col = col
def transform(self, df):
X = df.copy()
X[self.col] = X[self.col].map(lambda x: self.ordering.index(x))
return X
def fit(self, *_):
return self
# 調(diào)用自定義的填充器
ce = CustomEncoder(col='ordinal_column', ordering=['dislike','somewhat like','like'])
ce.fit_transform(X)

3)數(shù)值變量分箱操作
以上的內(nèi)容是對類別變量的一些簡單處理操作,也是比較常用的幾種,接下來我們就對數(shù)值變量進行一些簡單處理方法的講解。
有的時候,雖然變量值是連續(xù)的,但是只有轉(zhuǎn)換成類別才有解釋的可能,比如年齡,我們需要分成年齡段,這里我們可以使用pandas的 cut函數(shù)來實現(xiàn)。
# 數(shù)值變量處理——cut函數(shù)
class CustomCutter(TransformerMixin):
def __init__(self, col, bins, labels=False):
self.labels = labels
self.bins = bins
self.col = col
def transform(self, df):
X = df.copy()
X[self.col] = pd.cut(X[self.col], bins=self.bins, labels=self.labels)
return X
def fit(self, *_):
return self
# 調(diào)用自定義的填充器
cc = CustomCutter(col='quantitative_column', bins=3)
cc.fit_transform(X)

綜上,我們可以對上面自定義的方法一并在Pipeline中進行調(diào)用,Pipeline的順序為:
1)用imputer填充缺失值
2)獨熱編碼city和boolean
3)標簽編碼ordinal_column
4)分箱處理quantitative_column
代碼為:
from sklearn.pipeline import Pipeline
# 流水線封裝
pipe = Pipeline([('imputer',imputer),
('dummify',cd),
('encode',ce),
('cut',cc)
])
# 訓練流水線
pipe.fit(X)
# 轉(zhuǎn)換流水線
pipe.transform(X)
?? 數(shù)值變量擴展
這一小節(jié)我們使用一個新的數(shù)據(jù)集(人體胸部加速度數(shù)據(jù)集),我們先導(dǎo)入數(shù)據(jù):
# 人體胸部加速度數(shù)據(jù)集,標簽activity的數(shù)值為1-7
'''
1-在電腦前工作
2-站立、走路和上下樓梯
3-站立
4-走路
5-上下樓梯
6-與人邊走邊聊
7-站立著說話
'''
df = pd.read_csv('./data/activity_recognizer/1.csv', header=None)
df.columns = ['index','x','y','z','activity']
df.head()

這邊只介紹一種多項式生成新特征的辦法,調(diào)用PolynomialFeatures來實現(xiàn)。
# 擴展數(shù)值特征
from sklearn.preprocessing import PolynomialFeatures
x = df[['x','y','z']]
y = df['activity']
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)
x_poly = poly.fit_transform(x)
pd.DataFrame(x_poly, columns=poly.get_feature_names()).head()

還可以查看下衍生新變量后的相關(guān)性情況,顏色越深相關(guān)性越大:
# 查看熱力圖(顏色越深代表相關(guān)性越強)
%matplotlib inline
import seaborn as sns
sns.heatmap(pd.DataFrame(x_poly, columns=poly.get_feature_names()).corr())

在流水線中的實現(xiàn)代碼:
# 導(dǎo)入相關(guān)庫
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
knn = KNeighborsClassifier()
# 在流水線中使用
pipe_params = {'poly_features__degree':[1,2,3],
'poly_features__interaction_only':[True,False],
'classify__n_neighbors':[3,4,5,6]}
# 實例化流水線
pipe = Pipeline([('poly_features',poly),
('classify',knn)])
# 網(wǎng)格搜索
grid = GridSearchCV(pipe, pipe_params)
grid.fit(x,y)
print(grid.best_score_, grid.best_params_)
0.721189408065 {'classify__n_neighbors': 5, 'poly_features__degree': 2, 'poly_features__interaction_only': True}
?? 文本變量處理
文本處理一般在NLP(自然語言處理)領(lǐng)域應(yīng)用最為廣泛,一般都是需要把文本進行向量化,最為常見的方法有 詞袋(bag of words)、CountVectorizer、TF-IDF。
1)bag of words
詞袋法分成3個步驟,分別是分詞(tokenizing)、計數(shù)(counting)、歸一化(normalizing)。
2)CountVectorizer
將文本轉(zhuǎn)換為矩陣,每列代表一個詞語,每行代表一個文檔,所以一般出來的矩陣會是非常稀疏的,在sklearn.feature_extraction.text 中調(diào)用 CountVectorizer 即可使用。
3)TF-IDF
TF-IDF向量化器由兩個部分組成,分別為代表詞頻的TF部分,以及代表逆文檔頻率的IDF,這個TF-IDF是一個用于信息檢索和聚類的詞加權(quán)方法,在 sklearn.feature_extraction.text 中調(diào)用 TfidfVectorizer 即可。
TF:即Term Frequency,詞頻,也就是單詞在文檔中出現(xiàn)的頻率。
IDF:即Inverse Document Frequency,逆文檔頻率,用于衡量單詞的重要度,如果單詞在多份文檔中出現(xiàn),就會被降低權(quán)重。
? 04 特征選擇
好了,經(jīng)過了上面的特征衍生操作,我們現(xiàn)在擁有了好多好多的特征(變量)了,全部丟進去模型訓練好不好?當然是不行了??,這樣子既浪費資源又效果不佳,因此我們需要做一下 特征篩選 ,而特征篩選的方法大致可以分為兩大類:基于統(tǒng)計的特征篩選 和 基于模型的特征篩選。
在進行特征選擇之前,我們需要搞清楚一個概念:到底什么是更好的?有什么指標可以用來量化呢?
這大致也可以分為兩大類:一類是模型指標,比如accuracy、F1-score、R^2等等,還有一類是元指標,也就是指不直接與模型預(yù)測性能相關(guān)的指標,如:模型擬合/訓練所需的時間、擬合后的模型預(yù)測新實例所需要的時間、需要持久化(永久保存)的數(shù)據(jù)大小。
我們可以通過封裝一個方法,把上面提及到的指標封裝起來,方便后續(xù)的調(diào)用,代碼如下:
from sklearn.model_selection import GridSearchCV
def get_best_model_and_accuracy(model, params, x, y):
grid = GridSearchCV(model,
params,
error_score=0.)
grid.fit(x,y)
# 經(jīng)典的性能指標
print("Best Accuracy:{}".format(grid.best_score_))
# 得到最佳準確率的最佳參數(shù)
print("Best Parameters:{}".format(grid.best_params_))
# 擬合的平均時間
print("Average Time to Fit (s):{}".format(round(grid.cv_results_['mean_fit_time'].mean(), 3)))
# 預(yù)測的平均時間
print("Average Time to Score (s):{}".format(round(grid.cv_results_['mean_score_time'].mean(), 3)))
############### 使用示例 ###############
# 導(dǎo)入相關(guān)庫
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
knn = KNeighborsClassifier()
# 在流水線中使用
pipe_params = {'poly_features__degree':[1,2,3],
'poly_features__interaction_only':[True,False],
'classify__n_neighbors':[3,4,5,6]}
# 實例化流水線
pipe = Pipeline([('poly_features',poly),
('classify',knn)])
# 網(wǎng)格搜索
get_best_model_and_accuracy(pipe, pipe_params, x, y)
通過上面的操作,我們可以創(chuàng)建一個模型性能基準線,用于對比后續(xù)優(yōu)化的效果。接下來介紹一些常用的特征選擇方法。
1)基于統(tǒng)計的特征選擇
針對于單變量,我們可以采用 皮爾遜相關(guān)系數(shù)以及假設(shè)檢驗 來選擇特征。
(1)皮爾遜相關(guān)系數(shù)可以通過 corr() 來實現(xiàn),返回的值在-1到1之間,絕對值越大代表相關(guān)性越強;
(2)假設(shè)檢驗也就是p值,作為一種統(tǒng)計檢驗,在特征選擇中,假設(shè)測試得原則是:” 特征與響應(yīng)變量沒有關(guān)系“(零假設(shè))為真還是假。我們需要對每個變量進行檢測,檢測其與target有沒有顯著關(guān)系??梢允褂?SelectKBest 和 f_classif 來實現(xiàn)。一般P值是介于0-1之間,簡而言之,p值越小,拒絕零假設(shè)的概率就越大,也就是這個特征與target關(guān)系更大。
2)基于模型的特征選擇
(1)對于文本特征,sklearn.feature_extraction.text里的 CountVectorizer有自帶的特征篩選的參數(shù),分別是 max_features、min_df、max_df、stop_words,可以通過搜索這些參數(shù)來進行特征選擇,可以結(jié)合 SelectKBest 來實現(xiàn)流水線。
(2)針對??樹模型,我們可以直接調(diào)用不同樹模型算法里的 特征重要度 來返回特征重要度,比如 DecisionTreeClassifier里的feature_importances_,(除此之外還有RandomForest、GBDT、XGBoost、ExtraTreesClassifier等等)都可以直接返回每個特征對于本次擬合的重要度,從而我們可以剔除重要度偏低的特征,可以結(jié)合 SelectFromModel來實現(xiàn)流水線。
(3)使用正則化來篩選變量(針對線性模型)。有兩種常用的正則化方法:L1正則化(Lasso)和L2正則化(嶺)。
總結(jié)一下,有幾點做特征選擇的方法經(jīng)驗:
(1)如果特征是分類變量,那么可以從SelectKBest開始,用卡方或者基于樹的選擇器來選擇變量;
(2)如果特征是定量變量,可以直接用線性模型和基于相關(guān)性的選擇器來選擇變量;
(3)如果是二分類問題,可以考慮使用 SelectFromModel和SVC;
(4)在進行特征選擇前,還是需要做一下EDA。
?? 05 特征轉(zhuǎn)換
經(jīng)過了上面幾個環(huán)節(jié)的“洗禮”,我們來到特征轉(zhuǎn)換的環(huán)節(jié),也就是使用源數(shù)據(jù)集的隱藏結(jié)構(gòu)來創(chuàng)建新的列,常用的辦法有2種:PCA和LDA。
? PCA:
PCA,即主成分分析(Principal Components Analysis),是比較常見的數(shù)據(jù)壓縮的辦法,即將多個相關(guān)特征的數(shù)據(jù)集投影到相關(guān)特征較少的坐標系上。也就是說,轉(zhuǎn)換后的特征,在解釋性上就走不通了,因為你無法解釋這個新變量到底具有什么業(yè)務(wù)邏輯了。
PCA的原理這里就不展開來講了,太多的文章把它講得十分透徹了。這里主要是復(fù)現(xiàn)一下PCA在sklearn上的調(diào)用方法,一來繼續(xù)熟悉下Pipeline的使用,二來理解一下PCA的使用方法。
# 導(dǎo)入相關(guān)庫
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.decomposition import PCA
# 導(dǎo)入數(shù)據(jù)集
iris = load_iris()
iris_x, iris_y = iris.data, iris.target
# 實例化方法
pca = PCA(n_components=2)
# 訓練方法
pca.fit(iris_x)
pca.transform(iris_x)[:5,]
# 自定義一個可視化的方法
label_dict = {i:k for i,k in enumerate(iris.target_names)}
def plot(x,y,title,x_label,y_label):
ax = plt.subplot(111)
for label,marker,color in zip(
range(3),('^','s','o'),('blue','red','green')):
plt.scatter(x=x[:,0].real[y == label],
y = x[:,1].real[y == label],
color = color,
alpha = 0.5,
label = label_dict[label]
)
plt.xlabel(x_label)
plt.ylabel(y_label)
leg = plt.legend(loc='upper right', fancybox=True)
leg.get_frame().set_alpha(0.5)
plt.title(title)
# 可視化
plot(iris_x, iris_y,"original iris data","sepal length(cm)","sepal width(cm)")
plt.show()
plot(pca.transform(iris_x), iris_y,"Iris: Data projected onto first two PCA components","PCA1","PCA2")

以上是PCA在sklearn上的簡單調(diào)用和效果展示,另外,作者提出了一個很有意思的問題:
一般而言,對特征進行歸一化處理后會對機器學習算法的效果有比較明顯的幫助,但為什么在書本的例子卻是相反呢?
給出的解釋是:在對數(shù)據(jù)進行縮放后,列與列之間的協(xié)方差會更加一致,而且每個主成分解釋的方差會變得分散,而不是集中在某一個主成分上。所以,在實際操作的時候,都要對縮放的未縮放的數(shù)據(jù)進行性能測試才是最穩(wěn)妥的哦。
? LDA:
LDA,即線性判別分析(Linear Discriminant Analysis),它是一個有監(jiān)督的算法(哦對了, PCA是無監(jiān)督的),一般是用于分類流水線的預(yù)處理步驟。與PCA類似,LDA也是提取出一個新的坐標軸,將原始的高維數(shù)據(jù)投影到低維空間去,而區(qū)別在于LDA不會去專注數(shù)據(jù)之間的方差大小,而是直接優(yōu)化低維空間,以獲得最佳的類別可分性。
# LDA的使用
# 導(dǎo)入相關(guān)庫
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
# 實例化LDA模塊
lda = LinearDiscriminantAnalysis(n_components=2)
# 訓練數(shù)據(jù)
x_lda_iris = lda.fit_transform(iris_x, iris_y)
# 可視化
plot(x_lda_iris, iris_y, "LDA Projection", "LDA1", "LDA2")

?? 06 特征學習
來到最后一章了,這章的主題是“以AI促AI”??雌饋磉€蠻抽象的,反正我是覺得有點奇怪,特征學習算法是非參數(shù)方法,也就是不依賴數(shù)據(jù)結(jié)構(gòu)而構(gòu)建出來的新算法。
?? 數(shù)據(jù)的參數(shù)假設(shè)
參數(shù)假設(shè)指的是算法對數(shù)據(jù)形狀的基本假設(shè)。比如上一章的PCA,我們是假設(shè):
原始數(shù)據(jù)的形狀可以被(特征值)分解,并且可以用單個線性變換(矩陣計算)表示。
而特征學習算法,就是要去除這個“假設(shè)”來解決問題,因為這算法不會依賴數(shù)據(jù)的形狀,而是依賴于隨機學習(Stochastic Learning),指的是這些算法并不是每次輸出相同的結(jié)果,而是一次次按輪(epoch)去檢查數(shù)據(jù)點以找到要提取的最佳特征,并且可以擬合出一個最優(yōu)的解決方法。
而在特征學習領(lǐng)域,有兩種方法是比較常用的,也是下面來講解的內(nèi)容:受限玻爾茲曼機(RBM)和詞嵌入。
?? 受限玻爾茲曼機(RBM)
RBM是一種簡單的深度學習架構(gòu),是一組無監(jiān)督的特征學習算法,根據(jù)數(shù)據(jù)的概率模型學習一定數(shù)量的新特征,往往使用RBM之后去用線性模型(線性回歸、邏輯回歸、感知機等)的效果極佳。
從概念上說,RBM是一個淺層(2層)的神經(jīng)網(wǎng)絡(luò),屬于深度信念網(wǎng)絡(luò)(DBN,deep belief network)算法的一種。它也是一種無監(jiān)督算法,可以學習到的 特征數(shù)量只受限于計算能力,它可能學習到比原始要少或者多的特征,具體要學習的特征數(shù)量取決于要解決的問題。

“受限”的說法是因為它只允許層與層之間的連接(層間連接),而不允許同一層內(nèi)的節(jié)點連接(層內(nèi)連接)。

在這里需要理解一下“重建”(Reconstruction),也就是這個操作,使得在不涉及更深層網(wǎng)絡(luò)的情況下,可見層(輸入層)和隱含層之間可以存在數(shù)次的前向和反向傳播。
在重建階段,RBM會反轉(zhuǎn)網(wǎng)絡(luò),可見層變成了隱含層,隱含層變成了可見層,用相同的權(quán)重將激活變量a反向傳遞到可見層,但是偏差不一樣,然后用前向傳導(dǎo)的激活變量重建原始輸入向量。RBM就是用這種方法來進行“自我評估”的,通過將激活信息進行反向傳導(dǎo)并獲取原始輸入的近似值,該網(wǎng)絡(luò)可以調(diào)整權(quán)重,讓近似值更加接近原始輸入。
在訓練開始時,由于權(quán)重是隨機初始化的(一般做法),近似值與真實值的差異可能會極大的,接下來就會通過反向傳播的方法來調(diào)整權(quán)重,最小化原始輸入與近似值的距離,一直重復(fù)這個過程,直到近似值盡可能接近原始輸入。(這個過程發(fā)生的次數(shù)叫 迭代次數(shù) )
大致的原理就是上面的說法了,更加詳細的解釋可以自行百度哦。下面我們來講講RBM在機器學習管道中的應(yīng)用,我們還是使用MNIST數(shù)據(jù)集,這個數(shù)據(jù)集在之前講Keras的時候也用到了,就是一堆數(shù)字的像素點數(shù)據(jù),然后用來識別數(shù)字。
# RBM的使用
# 我們使用MNIST數(shù)據(jù)集來講解
# 導(dǎo)入相關(guān)庫
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import BernoulliRBM
from sklearn.pipeline import Pipeline
# 導(dǎo)入數(shù)據(jù)集
images = np.genfromtxt('./data/mnist_train.csv', delimiter=',')
print(images.shape)
# 劃分數(shù)據(jù)
images_x, images_y = images[:,1:], images[:,0]
# 縮放特征到0-1
images_x = images_x/255.
# 用RBM學習新特征
rbm = BernoulliRBM(random_state=0)
lr = LogisticRegression()
# 設(shè)置流水線的參數(shù)范圍
params = {'clf__C':[1e-1, 1e0, 1e1],
'rbm__n_components':[100, 200]
}
# 創(chuàng)建流水線
pipeline = Pipeline([('rbm', rbm),
('clf', lr)])
# 實例化網(wǎng)格搜索類
grid = GridSearchCV(pipeline, params)
# 擬合數(shù)據(jù)
grid.fit(images_x, images_y)
# 返回最佳參數(shù)
grid.best_params_, grid.best_score_
?? 詞嵌入
在NLP領(lǐng)域應(yīng)用極為廣泛了,它可以將字符串(單詞或短語)投影到n維特征集中,以便理解上下文和措辭的細節(jié),我們可以使用sklearn中的CountVectorizer 和 TfidfVectorizer 來將這些字符串進行轉(zhuǎn)為向量,但這只是一些單詞特征的集合而已,為了理解這些特征,我們更加要關(guān)注一個叫 gensim的包。
常用的詞嵌入方法有兩種:Word2vec和GloVe。
Word2vec: Google發(fā)明的一種基于深度學習的算法。Word2vec也是一個淺層的神經(jīng)網(wǎng)絡(luò),含有輸入層、隱含層和輸出層,其中輸入層和輸出層的節(jié)點個數(shù)一樣。
**GloVe: ** 來自斯坦福大學的算法,通過一系列矩陣統(tǒng)計進行學習。
詞嵌入的應(yīng)用很多,比如信息檢索,意思是當我們輸入關(guān)鍵詞時,搜索引擎可以回憶并準確返回和關(guān)鍵詞匹配的文章或者新聞。
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!