本節将介紹特征工程的一些常見示例:表示分類資料的特征、表示文本的特征和表示圖像的特征。另外,還會介紹提高模型複雜度的衍生特征和處理缺失資料的填充方法。這個過程通常被稱為向量化,因為它把任意格式的資料轉換成具有良好特性的向量形式。
1、分類資料
一種常見的非數值資料類型是分類資料。例如,浏覽房屋資料的時候,除了看到“房價”(price)和“面積”(rooms)之類的數值特征,還會有“地點”(neighborhood)資訊,資料可能像這樣:
data = [
{'price': 850000, 'rooms': 4, 'neighborhood': 'Queen Anne'},
{'price': 700000, 'rooms': 3, 'neighborhood': 'Fremont'},
{'price': 650000, 'rooms': 3, 'neighborhood': 'Wallingford'},
{'price': 600000, 'rooms': 2, 'neighborhood': 'Fremont'}
]
你可能會把分類特征用映射關系編碼成整數:
{'Queen Anne': 1, 'Fremont': 2, 'Wallingford': 3};
在Scikit-Learn 中這麼做并不是一個好辦法:這個程式包的所有子產品都有一個基本假設,那就是數值特征可以反映代數量(algebraic quantities)。是以,這樣映射編碼可能會讓人覺得存在

甚至還有
這顯然是沒有意義的。
面對這種情況,常用的解決方法是獨熱編碼。
它可以有效增加額外的列,讓0 和1 出現在對應的列分别表示每個分類值有或無。當你的資料是像上面那樣的字典清單時,用Scikit-Learn 的DictVectorizer 類就可以實作:
from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer(sparse=False, dtype=int)
vec.fit_transform(data)
neighborhood 字段轉換成三列來表示三個地點标簽,每一行中用1 所在的列對應一個地點。當這些分類特征編碼之後,你就可以和之前一樣拟合Scikit-Learn 模型了:
如果要看每一列的含義,可以用下面的代碼檢視特征名稱
vec.get_feature_names()
如果你的分類特征有許多枚舉值,那麼資料集的次元就會急劇增加。然而,由于被編碼的資料中有許多0,是以用稀疏矩陣表示會非常高效:
vec = DictVectorizer(sparse=True, dtype=int)
vec.fit_transform(data)
在拟合和評估模型時,Scikit-Learn 的許多(并非所有)評估器都支援稀疏矩陣輸入。sklearn.preprocessing.OneHotEncoder 和sklearn.feature_extraction.FeatureHasher 是Scikit-Learn 另外兩個為分類特征編碼的工具。
2、文本特征
另一種常見的特征工程需求是将文本轉換成一組數值。例如,絕大多數社交媒體資料的自動化采集,都是依靠将文本編碼成數字的技術手段。資料采集最簡單的編碼方法之一就是單詞統計。
sample = ['problem of evil',
'evil queen',
'horizon problem']
面對單詞統計的資料向量化問題時,可以建立一個列來表示單詞“problem”、單詞“evil”和單詞“horizon”等。雖然手動做也可以,但是用Scikit-Learn 的CountVectorizer 更是可以輕松實作:
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer()
X = vec.fit_transform(sample)
X
結果是一個稀疏矩陣,裡面記錄了每個短語中每個單詞的出現次數。用帶列标簽的DataFrame 來表示這個稀疏矩陣
import pandas as pd
pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
不過這種統計方法也有一些問題:原始的單詞統計會讓一些常用詞聚集太高的權重,在分類算法中這樣并不合理。解決這個問題的方法就是通過TF–IDF(term frequency–inversedocument frequency,詞頻逆文檔頻率),通過單詞在文檔中出現的頻率來衡量其權重
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer()
X = vec.fit_transform(sample)
pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
3、圖像特征
機器學習還有一種常見需求,那就是對圖像進行編碼。我們在處理手寫數字圖像時使用的方法,也是最簡單的圖像編碼方法:用像素表示圖像。
但是在其他類型的任務中,這類方法可能不太合适。雖然完整地介紹圖像特征的提取技術超出了本章的範圍,但是你可以在Scikit-Image 項目(
http://scikit-image.org)中找到許多标準方法的高品質實作。關于同時使用Scikit-Learn 和Scikit-Image 的示例,請參見應用 人臉識别管道。
4、衍生特征
還有一種有用的特征是輸入特征經過數學變換衍生出來的新特征。
将一個線性回歸轉換成多項式回歸時,并不是通過改變模型來實作,而是通過改變輸入資料!這種處理方式有時被稱為基函數回歸(basis function regression)。
例如,下面的資料顯然不能用一條直線描述
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
x = np.array([1, 2, 3, 4, 5])
y = np.array([4, 2, 1, 3, 7])
plt.scatter(x, y);
但是我們仍然用LinearRegression 拟合出一條直線,并獲得直線的最優解:
from sklearn.linear_model import LinearRegression
X = x[:, np.newaxis]
model = LinearRegression().fit(X, y)
yfit = model.predict(X)
plt.scatter(x, y)
plt.plot(x, yfit);
很顯然,我們需要用一個更複雜的模型來描述x 與y 的關系。可以對資料進行變換,并增加額外的特征來提升模型的複雜度。例如,可以在資料中增加多項式特征:
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=3, include_bias=False)
X2 = poly.fit_transform(X)
print(X2)
在衍生特征矩陣中,第1 清單示 x;
第2清單示 x²;
第3 清單示 x³;
通過對這個擴充的輸入矩陣計算線性回歸,就可以獲得更接近原始資料的結果了。
model = LinearRegression().fit(X2, y)
yfit = model.predict(X2)
plt.scatter(x, y)
plt.plot(x, yfit);
這種不通過改變模型,而是通過變換輸入來改善模型效果的理念,正是許多更強大的機器學習方法的基礎。
5、缺失值填充
特征工程中還有一種常見需求是處理缺失值。例如,有如下一個資料集:
from numpy import nan
X = np.array([[ nan, 0, 3 ],
[ 3, 7, 9 ],
[ 3, 5, 2 ],
[ 4, nan, 6 ],
[ 8, 8, 1 ]])
y = np.array([14, 16, -1, 8, -5])
當将一個普通的機器學習模型應用到這份資料時,首先需要用适當的值替換這些缺失資料。這個操作被稱為缺失值填充,相應的政策很多,有的簡單(例如用列均值替換缺失值),有的複雜(例如用矩陣填充或其他模型來處理缺失值)。
對于一般的填充方法,如均值、中位數、衆數,Scikit-Learn 有Imputer 類可以實作:
from sklearn.preprocessing import Imputer
imp = Imputer(strategy='mean')
X2 = imp.fit_transform(X)
X2
結果矩陣中的兩處缺失值都被所在列剩餘資料的均值替代了。這個被填充的資料就可以直接放到評估器裡訓練了,例如LinearRegression 評估器:
model = LinearRegression().fit(X2, y)
model.predict(X2)
6、特征管道
如果經常需要手動應用前文介紹的任意一種方法,你很快就會感到厭倦,尤其是當你需要将多個步驟串起來使用時。例如,我們可能需要對一些資料做如下操作。
用均值填充缺失值。
将衍生特征轉換為二次方。
拟合線性回歸模型。
Scikit-Learn 提供了一個管道對象,如下所示:
from sklearn.pipeline import make_pipeline
model = make_pipeline(Imputer(strategy='mean'),
PolynomialFeatures(degree=2),
LinearRegression())
這個管道看起來就像一個标準的Scikit-Learn 對象,可以對任何輸入資料進行所有步驟的處理:
model.fit(X, y) # X with missing values, from above
print(y)
print(model.predict(X))
這樣的話,所有的步驟都會自動完成。請注意,出于簡化示範考慮,将模型應用到已經訓練過的資料上,模型能夠非常完美地預測結果。