手把手教你入門和實踐特徵工程 的全方位萬字筆記,附程式碼下載
- 2019 年 10 月 4 日
- 筆記
說起特徵工程,都說是機器學習建模中最為重要而且費時的一項工作,而且它涉及的知識點會非常地多,經驗老道的老司機自然是輕車熟路了,但對於剛剛入門的新手司機,學習到的知識點都是東一點西一點的,不夠系統化,本篇文章是在閱讀了一本評分極高的特徵工程書籍 ? 《特徵工程入門與實踐》後的一篇筆記文,記錄下相對比較系統的知識點以及可運行復現的程式碼,希望對各位同行有所幫助哈。

圖:強力推薦這本書
大家可以先看下思維導圖:

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

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

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

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

最後把上面的內容總結一下:

3. 關鍵程式碼彙集
以下的程式碼只是核心片段,完整程式碼可在公眾號(SAMshare)後台輸入關鍵字 特徵工程 獲取。
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) # 檢查缺失個數 climate.isnull().sum()
3)變數類別轉換
# 日期轉換, 將dt 轉換為日期,取年份, 注意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 特徵增強
這一步其實就是數據清洗了,雖然上一步中也有涉及到部分清洗工作(比如清除空值、日期轉換之類的),但卻是分散的,這節重點講講數據清洗的一些技巧和實踐程式碼,供大家在實際項目中去使用。
Step1: 進行EDA(Exploratory Data Analysis),思路如下:
(1)首先看看目標佔比情況(針對二分類問題,也就是0和1的佔比情況),直接 value_counts()就可以解決,看看樣本是否失衡。
(2)接著看看有沒有空值,直接統計 isnull().sum() 的個數,不過需要注意的是,可能統計出來沒有缺失,並不是因為真的沒有缺失,而且缺失被人用某個特殊值填充了,一般會用 -9、blank、unknown、0之類的,需要注意⚠️識別,後面需要對缺失進行合理填充。
(2.1)怎麼識別缺失值呢?一般可以通過 data.describe() 獲取基本的描述性統計,根據均值、標準差、極大極小值等指標,結合變數含義來判斷。

(3)再接著看不同類別之間的特徵值分布情況,可通過畫直方圖(數值型變數)和計算變數值佔比分布(類別變數)來觀察。
(4)觀察不同變數之間的相關性情況,可以通過繪製 相關矩陣的熱力圖 來觀察大體情況。
Step2: 處理數據缺失問題
缺失處理的辦法有好多種,但最為常用的作者講到有兩種:填充和刪除。
而在處理缺失前,我們在上面的小節中識別出來了部分被人工填充的缺失,
需要還原一下:
# 處理被錯誤填充的缺失值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() 來處理即可,同時我們還可以檢查下我們到底刪除了多少數據量:round(data.shape[0]-data_dropped.shape[0])/float(data.shape[0]) 就可以統計出來了。當然,刪除之後,我們還需要看看數據的分布,對比目標佔比、特徵分布與先前的是否存在明顯差異,如果是的話,建議不要使用這種辦法。

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

從上圖中我們可以看出一個問題,那就是每個特徵之間的量綱都是不一樣的,這對於knn這種基於距離的模型來說是「致命」的bug,因此我們需要進行標準化和歸一化處理。
我們重點關注3種方法:
1)Z分數標準化
最為常用的標準化技術,利用了統計學中的z分數思想,也就是將數據轉換為均值為0,標準差為1的分布,其在python中的調用方法:
# z分數標準化(單一特徵) from sklearn.preprocessing import StandardScaler # 實例化方法 scaler = StandardScaler() glucose_z_score_standarScaler =scaler.fit_transform(pima[['plasma_glucose_concentration']].fillna(-9)) # 可以看看轉換之後的均值和標準差是否為0和1 glucose_z_score_standarScaler.mean(), glucose_z_score_standarScaler.std() # z分數標準化(全部特徵) 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中的調用方法:
# 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)行歸一化
行歸一化針對的是每一行數據,不同於上面的兩種方法(針對列),對行進行處理是為了保證每行的向量長度一樣(也就是單位範圍,unit norm),有L1、L2範數。
在python中的調用方法:
# 行歸一化 from sklearn.preprocessing import Normalizer # 實例化方法 normalize = Normalizer() # 使用行歸一化 pima_normalized = pd.DataFrame(normalize.fit_transform(pima.fillna(-9)),columns=pima_columns) # 查看矩陣的平均範數(1) np.sqrt((pima_normalized**2).sum(axis=1)).mean()
03 特徵構建
如果我們對變數進行處理之後,效果仍不是非常理想,就需要進行特徵構建了,也就是衍生新變數。
而在這之前,我們需要了解我們的數據集,先前兩節中我們了解到了可以通過 data.info 和 data.describe() 來查看,同時結合數據等級(定類、定序、定距、定比)來理解變數。
基礎操作
本小節中我們使用一個自定義數據集。
# 本次案例使用的數據集 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
首先我們需要對分類變數進行填充操作,類別變數一般用眾數或者特殊值來填充,回顧之前的內容,我們也還是採取Pipeline的方式來進行,因此可以事先基於TransformMixin基類來對填充的方法進行封裝,然後直接在Pipeline中進行調用,程式碼可以參考:
# 填充分類變數(基於TransformerMixin的自定義填充器,用眾數填充) 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 # 調用自定義的填充器 cci = CustomCategoryzImputer(cols=['city','boolean']) cci.fit_transform(X)

又或者利用 scikit-learn 的 Imputer類來實現填充,而這個類有一個 Strategy的方法自然就被繼承過來用了,包含的有mean、median、most_frequent可供選擇。
# 填充分類變數(基於Imputer的自定義填充器,用眾數填充) 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 # 調用自定義的填充器 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)完成了分類變數的填充工作,接下來就需要對分類變數進行編碼了(因為大多數的機器學習演算法都是無法直接對類別變數進行計算的),一般有兩種辦法:獨熱編碼以及標籤編碼。
1)獨熱編碼
獨熱編碼主要是針對定類變數的,也就是不同變數值之間是沒有順序大小關係的,我們一般可以使用 scikit_learn 裡面的 OneHotEncoding來實現的,但我們這裡還是使用自定義的方法來加深理解。
# 類別變數的編碼(獨熱編碼) 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 # 調用自定義的填充器 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 # 調用自定義的填充器 ce = CustomEncoder(col='ordinal_column', ordering=['dislike','somewhat like','like']) ce.fit_transform(X)

3)數值變數分箱操作
以上的內容是對類別變數的一些簡單處理操作,也是比較常用的幾種,接下來我們就對數值變數進行一些簡單處理方法的講解。
有的時候,雖然變數值是連續的,但是只有轉換成類別才有解釋的可能,比如年齡,我們需要分成年齡段,這裡我們可以使用pandas的 cut函數來實現。
# 數值變數處理——cut函數 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 # 調用自定義的填充器 cc = CustomCutter(col='quantitative_column', bins=3) cc.fit_transform(X)

綜上,我們可以對上面自定義的方法一併在Pipeline中進行調用,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) # 轉換流水線 pipe.transform(X)? 數值變數擴展
這一小節我們使用一個新的數據集(人體胸部加速度數據集),我們先導入數據:
# 人體胸部加速度數據集,標籤activity的數值為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()
這邊只介紹一種多項式生成新特徵的辦法,調用PolynomialFeatures來實現。
# 擴展數值特徵 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()

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

在流水線中的實現程式碼:
# 導入相關庫 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)]) # 網格搜索 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(自然語言處理)領域應用最為廣泛,一般都是需要把文本進行向量化,最為常見的方法有 詞袋(bag of words)、CountVectorizer、TF-IDF。
1)bag of words
詞袋法分成3個步驟,分別是分詞(tokenizing)、計數(counting)、歸一化(normalizing)。
2)CountVectorizer
將文本轉換為矩陣,每列代表一個詞語,每行代表一個文檔,所以一般出來的矩陣會是非常稀疏的,在sklearn.feature_extraction.text 中調用 CountVectorizer 即可使用。
3)TF-IDF
TF-IDF向量化器由兩個部分組成,分別為代表詞頻的TF部分,以及代表逆文檔頻率的IDF,這個TF-IDF是一個用於資訊檢索和聚類的詞加權方法,在 sklearn.feature_extraction.text 中調用 TfidfVectorizer 即可。
TF:即Term Frequency,詞頻,也就是單詞在文檔中出現的頻率。 IDF:即Inverse Document Frequency,逆文檔頻率,用于衡量單詞的重要度,如果單詞在多份文檔中出現,就會被降低權重。
04 特徵選擇
好了,經過了上面的特徵衍生操作,我們現在擁有了好多好多的特徵(變數)了,全部丟進去模型訓練好不好?當然是不行了?,這樣子既浪費資源又效果不佳,因此我們需要做一下 特徵篩選 ,而特徵篩選的方法大致可以分為兩大類:基於統計的特徵篩選 和 基於模型的特徵篩選。
在進行特徵選擇之前,我們需要搞清楚一個概念:到底什麼是更好的?有什麼指標可以用來量化呢?
這大致也可以分為兩大類:一類是模型指標,比如accuracy、F1-score、R^2等等,還有一類是元指標,也就是指不直接與模型預測性能相關的指標,如:模型擬合/訓練所需的時間、擬合後的模型預測新實例所需要的時間、需要持久化(永久保存)的數據大小。
我們可以通過封裝一個方法,把上面提及到的指標封裝起來,方便後續的調用,程式碼如下:
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) # 經典的性能指標 print("Best Accuracy:{}".format(grid.best_score_)) # 得到最佳準確率的最佳參數 print("Best Parameters:{}".format(grid.best_params_)) # 擬合的平均時間 print("Average Time to Fit (s):{}".format(round(grid.cv_results_['mean_fit_time'].mean(), 3))) # 預測的平均時間 print("Average Time to Score (s):{}".format(round(grid.cv_results_['mean_score_time'].mean(), 3))) ############### 使用示例 ############### # 導入相關庫 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)]) # 網格搜索 get_best_model_and_accuracy(pipe, pipe_params, x, y)通過上面的操作,我們可以創建一個模型性能基準線,用於對比後續優化的效果。接下來介紹一些常用的特徵選擇方法。
1)基於統計的特徵選擇
針對於單變數,我們可以採用 皮爾遜相關係數以及假設檢驗 來選擇特徵。
(1)皮爾遜相關係數可以通過 corr() 來實現,返回的值在-1到1之間,絕對值越大代表相關性越強;
(2)假設檢驗也就是p值,作為一種統計檢驗,在特徵選擇中,假設測試得原則是:」 特徵與響應變數沒有關係「(零假設)為真還是假。我們需要對每個變數進行檢測,檢測其與target有沒有顯著關係。可以使用 SelectKBest 和 f_classif 來實現。一般P值是介於0-1之間,簡而言之,p值越小,拒絕零假設的概率就越大,也就是這個特徵與target關係更大。
2)基於模型的特徵選擇
(1)對於文本特徵,sklearn.feature_extraction.text里的 CountVectorizer有自帶的特徵篩選的參數,分別是 max_features、min_df、max_df、stop_words,可以通過搜索這些參數來進行特徵選擇,可以結合 SelectKBest 來實現流水線。
(2)針對?樹模型,我們可以直接調用不同樹模型演算法里的 特徵重要度 來返回特徵重要度,比如 DecisionTreeClassifier里的feature_importances_,(除此之外還有RandomForest、GBDT、XGBoost、ExtraTreesClassifier等等)都可以直接返回每個特徵對於本次擬合的重要度,從而我們可以剔除重要度偏低的特徵,可以結合 SelectFromModel來實現流水線。
(3)使用正則化來篩選變數(針對線性模型)。有兩種常用的正則化方法:L1正則化(Lasso)和L2正則化(嶺)。
總結一下,有幾點做特徵選擇的方法經驗:
(1)如果特徵是分類變數,那麼可以從SelectKBest開始,用卡方或者基於樹的選擇器來選擇變數;
(2)如果特徵是定量變數,可以直接用線性模型和基於相關性的選擇器來選擇變數;
(3)如果是二分類問題,可以考慮使用 SelectFromModel和SVC;
(4)在進行特徵選擇前,還是需要做一下EDA。
05 特徵轉換
經過了上面幾個環節的「洗禮」,我們來到特徵轉換的環節,也就是使用源數據集的隱藏結構來創建新的列,常用的辦法有2種:PCA和LDA。
PCA:
PCA,即主成分分析(Principal Components Analysis),是比較常見的數據壓縮的辦法,即將多個相關特徵的數據集投影到相關特徵較少的坐標繫上。也就是說,轉換後的特徵,在解釋性上就走不通了,因為你無法解釋這個新變數到底具有什麼業務邏輯了。
PCA的原理這裡就不展開來講了,太多的文章把它講得十分透徹了。這裡主要是復現一下PCA在sklearn上的調用方法,一來繼續熟悉下Pipeline的使用,二來理解一下PCA的使用方法。
# 導入相關庫 from sklearn.datasets import load_iris import matplotlib.pyplot as plt %matplotlib inline from sklearn.decomposition import PCA # 導入數據集 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上的簡單調用和效果展示,另外,作者提出了一個很有意思的問題:
一般而言,對特徵進行歸一化處理後會對機器學習演算法的效果有比較明顯的幫助,但為什麼在書本的例子卻是相反呢?
給出的解釋是:在對數據進行縮放後,列與列之間的協方差會更加一致,而且每個主成分解釋的方差會變得分散,而不是集中在某一個主成分上。所以,在實際操作的時候,都要對縮放的未縮放的數據進行性能測試才是最穩妥的哦。
LDA:
LDA,即線性判別分析(Linear Discriminant Analysis),它是一個有監督的演算法(哦對了, PCA是無監督的),一般是用於分類流水線的預處理步驟。與PCA類似,LDA也是提取出一個新的坐標軸,將原始的高維數據投影到低維空間去,而區別在於LDA不會去專註數據之間的方差大小,而是直接優化低維空間,以獲得最佳的類別可分性。
# LDA的使用 # 導入相關庫 from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # 實例化LDA模組 lda = LinearDiscriminantAnalysis(n_components=2) # 訓練數據 x_lda_iris = lda.fit_transform(iris_x, iris_y) # 可視化 plot(x_lda_iris, iris_y, "LDA Projection", "LDA1", "LDA2")

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

「受限」的說法是因為它只允許層與層之間的連接(層間連接),而不允許同一層內的節點連接(層內連接)。

在這裡需要理解一下「重建」(Reconstruction),也就是這個操作,使得在不涉及更深層網路的情況下,可見層(輸入層)和隱含層之間可以存在數次的前向和反向傳播。
在重建階段,RBM會反轉網路,可見層變成了隱含層,隱含層變成了可見層,用相同的權重將激活變數a反向傳遞到可見層,但是偏差不一樣,然後用前向傳導的激活變數重建原始輸入向量。RBM就是用這種方法來進行「自我評估」的,通過將激活資訊進行反向傳導並獲取原始輸入的近似值,該網路可以調整權重,讓近似值更加接近原始輸入。
在訓練開始時,由於權重是隨機初始化的(一般做法),近似值與真實值的差異可能會極大的,接下來就會通過反向傳播的方法來調整權重,最小化原始輸入與近似值的距離,一直重複這個過程,直到近似值儘可能接近原始輸入。(這個過程發生的次數叫 迭代次數 )
大致的原理就是上面的說法了,更加詳細的解釋可以自行百度哦。下面我們來講講RBM在機器學習管道中的應用,我們還是使用MNIST數據集,這個數據集在之前講Keras的時候(傳送門)也用到了,就是一堆數字的像素點數據,然後用來識別數字。
# RBM的使用 # 我們使用MNIST數據集來講解 # 導入相關庫 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 # 導入數據集 images = np.genfromtxt('./data/mnist_train.csv', delimiter=',') print(images.shape) # 劃分數據 images_x, images_y = images[:,1:], images[:,0] # 縮放特徵到0-1 images_x = images_x/255. # 用RBM學習新特徵 rbm = BernoulliRBM(random_state=0) lr = LogisticRegression() # 設置流水線的參數範圍 params = {'clf__C':[1e-1, 1e0, 1e1], 'rbm__n_components':[100, 200] } # 創建流水線 pipeline = Pipeline([('rbm', rbm), ('clf', lr)]) # 實例化網格搜索類 grid = GridSearchCV(pipeline, params) # 擬合數據 grid.fit(images_x, images_y) # 返回最佳參數 grid.best_params_, grid.best_score_詞嵌入
在NLP領域應用極為廣泛了,它可以將字元串(單詞或短語)投影到n維特徵集中,以便理解上下文和措辭的細節,我們可以使用sklearn中的CountVectorizer 和 TfidfVectorizer 來將這些字元串進行轉為向量,但這只是一些單詞特徵的集合而已,為了理解這些特徵,我們更加要關注一個叫 gensim的包。
常用的詞嵌入方法有兩種:Word2vec和GloVe。
Word2vec:Google發明的一種基於深度學習的演算法。Word2vec也是一個淺層的神經網路,含有輸入層、隱含層和輸出層,其中輸入層和輸出層的節點個數一樣。
GloVe:來自斯坦福大學的演算法,通過一系列矩陣統計進行學習。
詞嵌入的應用很多,比如資訊檢索,意思是當我們輸入關鍵詞時,搜索引擎可以回憶並準確返回和關鍵詞匹配的文章或者新聞。