05-01 特徵預處理

更新、更全的《機器學習》的更新網站,更有python、go、數據結構與算法、爬蟲、人工智能教學等着你:http://www.javashuo.com/article/p-vozphyqp-cm.htmlpython

特徵預處理

以前說到構建機器學習系統的步驟中的第二步說到須要進行數據預處理,可是並無說如何對數據進行預處理,這一章將會展開來講說未來建模時會碰到的各類髒數據的形式,以及對這種形式數據的處理方式,而對數據處理即對數據的特徵進行處理。算法

1、特徵預處理學習目標

  1. 缺失值處理
  2. 離羣值處理
  3. 數據類型轉換
  4. 歸一化數據
  5. 二值化數據

2、特徵預處理詳解

2.1 缺失值處理

現實生活中的數據每每是不全面的,不少樣本的屬性值會有缺失,例如某我的填寫的我的信息不完整或者對我的隱私的保護政策致使建模時可能沒法獲得所須要的特徵,尤爲是在數據量較大時,這種缺失值的產生會對模型的性能形成很大的影響。接下來將經過鳶尾花數據討論缺失值處理的方法。數組

# 缺失值處理示例
import pandas as pd
from io import StringIO

iris_data = '''
5.1,,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,,Iris-versicolor
,,,,Iris-setosa
'''

df = pd.read_csv(StringIO(iris_data), header=None)
# 強調:以後的代碼只爲方便中文閱讀習慣的讀者看起來方便,本身寫代碼特證屬性名儘可能不要用中文,不方便變量名的建立
df.columns = ['花萼長度', '花萼寬度', '花瓣長度', '花瓣寬度', 'class_label']
df = df.iloc[:, :4]
df
花萼長度 花萼寬度 花瓣長度 花瓣寬度
0 5.1 NaN 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 NaN 0.2
3 7.0 3.2 4.7 1.4
4 6.4 3.2 4.5 1.5
5 6.9 3.1 4.9 NaN
6 NaN NaN NaN NaN

經過給定的鳶尾花數據,使用StringIO把字符串緩存成爲文本,以後把該數據讀入pandas。從pandas打印的結果能夠明顯的看到給出的數據中有3個NAN即缺失值。因爲數據少,很容易看出有幾個缺失值而且能夠手動改變,可是工業上數據量是很是龐大的,這個時候能夠調用pandas的isnull()。isnull()方法把數據集中存在的值看做True,缺失值看做False。緩存

df.isnull()
花萼長度 花萼寬度 花瓣長度 花瓣寬度
0 False True False False
1 False False False False
2 False False True False
3 False False False False
4 False False False False
5 False False False True
6 True True True True

經過在isnull()方法後使用sum()方法便可得到該數據集某個特徵含有多少個缺失值。安全

df.isnull().sum()
花萼長度    1
花萼寬度    2
花瓣長度    2
花瓣寬度    2
dtype: int64

2.1.1 刪除缺失值

處理缺失值最簡單也是最暴力的方法即是刪除含有缺失值的樣本或者特徵(注:工業上數據很是重要,通常不推薦這樣作),可使用dropna()方法並經過它的參數axis選擇刪除樣本仍是特徵,若是axis=1即刪除特徵;若是axis=0即刪除行,how='all'刪除全部特徵全爲缺失值的樣本,thresh=4刪除特徵值數小於4的樣本,subset=['花瓣寬度']刪除花瓣寬度特徵中有缺失值的樣本。數據結構

# axis=0刪除有NaN值的行
df.dropna(axis=0)
花萼長度 花萼寬度 花瓣長度 花瓣寬度
1 4.9 3.0 1.4 0.2
3 7.0 3.2 4.7 1.4
4 6.4 3.2 4.5 1.5
# axis=1刪除有NaN值的列
df.dropna(axis=1)
0
1
2
3
4
5
6
# 刪除全爲NaN值得行或列
df.dropna(how='all')
花萼長度 花萼寬度 花瓣長度 花瓣寬度
0 5.1 NaN 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 NaN 0.2
3 7.0 3.2 4.7 1.4
4 6.4 3.2 4.5 1.5
5 6.9 3.1 4.9 NaN
# 刪除行不爲4個值的
df.dropna(thresh=4)
花萼長度 花萼寬度 花瓣長度 花瓣寬度
1 4.9 3.0 1.4 0.2
3 7.0 3.2 4.7 1.4
4 6.4 3.2 4.5 1.5
# 刪除花萼寬度中有NaN值的數據
df.dropna(subset=['花萼寬度'])
花萼長度 花萼寬度 花瓣長度 花瓣寬度
1 4.9 3.0 1.4 0.2
2 4.7 3.2 NaN 0.2
3 7.0 3.2 4.7 1.4
4 6.4 3.2 4.5 1.5
5 6.9 3.1 4.9 NaN

2.1.2 填充缺失值

因爲工業上的數據來之不易是很重要的,因此對缺失值的處理最經常使用的方法是填充缺失值。填充缺失值可使用中位數、衆數、平均數與自定義固定值,具體用哪種沒有硬性的要求,具體問題具體分析。本節將經過scikit-learn的Imputer給出填充平均值的方法。app

# 填充缺失值示例
from sklearn.impute import SimpleImputer
import numpy as np

# 對全部缺失值填充固定值0
# imputer = SimpleImputer(missing_values=np.nan, strategy='constant', fill_value=0)

# 中位數strategy=median,衆數strategy=most_frequent
imputer = SimpleImputer(missing_values=np.nan, strategy='mean')
imputer = imputer.fit(df.values)
imputed_data = imputer.transform(df.values)
imputed_data
array([[5.1       , 3.14      , 1.4       , 0.2       ],
       [4.9       , 3.        , 1.4       , 0.2       ],
       [4.7       , 3.2       , 3.38      , 0.2       ],
       [7.        , 3.2       , 4.7       , 1.4       ],
       [6.4       , 3.2       , 4.5       , 1.5       ],
       [6.9       , 3.1       , 4.9       , 0.7       ],
       [5.83333333, 3.14      , 3.38      , 0.7       ]])

2.2 離羣值處理

離羣值又稱爲異常值,即不合理的值。假設某個數據集有x1與x2兩個特徵,以下圖所示:dom

# 離羣值處理示例
import random
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

%matplotlib inline
font = FontProperties(fname='/Library/Fonts/Heiti.ttc')

x1 = [i for i in range(6)]
x2 = x1

plt.scatter(x1, x2, color='b', label='正常值')
plt.scatter(x1, [i+random.random() for i in x1.copy()] , color='b')
plt.scatter(1.5, 8, color='r', label='離羣值')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend(prop=font)
# plt.savefig('img/離羣值.png', dpi=300)
plt.show()

png

2.2.1 獲取離羣值

  1. 統計分析
    對特徵值統計後分析判斷哪些值是不符合邏輯的,拿年齡舉例,若是發現某我的的年齡是200,至少目前是不合理的,所以能夠設定一個條件,把年齡大於200的數據都排除掉。機器學習

  2. 機率分佈原則
    根據高斯分佈可知距離平均值\(3\delta\)以外的機率爲0.003,這在統計學中屬於極小機率事件,所以能夠把超過該距離的值看成異常值處理。固然,你也能夠手動設定這個距離或機率,具體問題具體分析。

2.2.2 離羣值處理

每每能夠用上述兩種方法獲取離羣值,獲取離羣值以後的處理方式如同缺失值同樣,能夠把離羣值設置爲空值NaN,而後使用與處理缺失值一樣的方法處理離羣值,此處很少在贅述。

2.3 數據類型轉換

現有一個汽車樣本集,經過這個汽車樣本集能夠判斷人們是否會購買該汽車。可是這個樣本集的特徵值是離散型的,爲了確保計算機能正確讀取該離散值的特徵,須要給這些特徵作編碼處理,即建立一個映射表。若是特徵值分類較少,能夠選擇自定義一個字典存放特徵值與自定義值的關係。

car_data='''
乘坐人數,後備箱大小,安全性,是否能夠接受
4,med,high,acc
2,big,low,unacc
4,big,med,acc
4,big,high,acc
6,small,low,unacc
6,small,med,unacc
'''

df = pd.read_csv(StringIO(car_data), header=0)
df
乘坐人數 後備箱大小 安全性 是否能夠接受
0 4 med high acc
1 2 big low unacc
2 4 big med acc
3 4 big high acc
4 6 small low unacc
5 6 small med unacc

2.3.1 自定義數據類型編碼

此處只拿汽車安全性(safety)舉例。

safety_mapping = {
    'low':0,
    'med':1,
    'high':2,
}

df['安全性'] = df['安全性'].map(safety_mapping)
df
乘坐人數 後備箱大小 安全性 是否能夠接受
0 4 med 2 acc
1 2 big 0 unacc
2 4 big 1 acc
3 4 big 2 acc
4 6 small 0 unacc
5 6 small 1 unacc

對上述字典作反向映射處理,便可反向映射回原來的離散類型的特徵值。

inverse_safety_mapping = {v: k for k, v in safety_mapping.items()}
df['安全性'].map(inverse_safety_mapping)
0    high
1     low
2     med
3    high
4     low
5     med
Name: 安全性, dtype: object

2.3.2 scikit-learn數據類型編碼

目前LabelEncoder支持對一維數組進行編碼,有興趣的同窗能夠經過上述映射的寫法自定義封裝fit和transform方法寫一個對多個特徵編碼的LabelEncoder。此處只對後備箱大小屬性和是否能夠接受標籤信息作編碼處理。

# scikit-learn數據類型編碼示例
from sklearn.preprocessing import LabelEncoder

X_label_encoder = LabelEncoder()
X = df[['乘坐人數', '後備箱大小', '安全性']].values
X[:, 1] = X_label_encoder.fit_transform(X[:, 1])

X
array([[4, 1, 2],
       [2, 0, 0],
       [4, 0, 1],
       [4, 0, 2],
       [6, 2, 0],
       [6, 2, 1]], dtype=object)
y_label_encoder = LabelEncoder()
y = y_label_encoder.fit_transform(df['是否能夠接受'].values)
y
array([0, 1, 0, 0, 1, 1])

與字典映射同理,可使用inverse_transform()方法對數據作反向映射處理。

y_label_encoder.inverse_transform(y)
array(['acc', 'unacc', 'acc', 'acc', 'unacc', 'unacc'], dtype=object)

2.3.3 獨熱編碼

假設汽車安全性只是一個衡量標準,沒有特定的順序。可是計算機頗有可能把這些\(0,1,2\)做一個特定排序或者所以區分它們的重要性,這個時候就得考慮建立一個二進制值分別表示low、med、high這三個屬性值,有爲1,沒有爲0,例如\(010\)表示爲med。

scikit-learn中的OneHotEncoder能夠實現這種編碼處理。

# 獨熱編碼示例
from sklearn.preprocessing import OneHotEncoder

one_hot_encoder = OneHotEncoder(categories='auto')
one_hot_encoder.fit_transform(X).toarray()
array([[0., 1., 0., 0., 1., 0., 0., 0., 1.],
       [1., 0., 0., 1., 0., 0., 1., 0., 0.],
       [0., 1., 0., 1., 0., 0., 0., 1., 0.],
       [0., 1., 0., 1., 0., 0., 0., 0., 1.],
       [0., 0., 1., 0., 0., 1., 1., 0., 0.],
       [0., 0., 1., 0., 0., 1., 0., 1., 0.]])

使用categories對單個屬性進行獨熱編碼。

one_hot_encoder = OneHotEncoder(categories=[['med','big','small']])
# 可在OneHotEncoder類中加入sparseFale參數等同於toarray()方法
one_hot_encoder.fit_transform(df[['後備箱大小']]).toarray()
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.],
       [0., 0., 1.],
       [0., 0., 1.]])

使用pandas對字符串屬性獨熱編碼,若是屬性值爲數值型則不進行獨熱編碼。

pd.get_dummies(df[['乘坐人數', '後備箱大小', '安全性']])
乘坐人數 安全性 後備箱大小_big 後備箱大小_med 後備箱大小_small
0 4 2 0 1 0
1 2 0 1 0 0
2 4 1 1 0 0
3 4 2 1 0 0
4 6 0 0 0 1
5 6 1 0 0 1

使用獨熱編碼在解決特徵值無序性的同時也增長了特徵數,這無疑會給將來的計算增長難度,所以能夠適當減小沒必要要的維度。例如當爲後備箱進行獨熱編碼的時候會有後備箱大小_big、後備箱大小_med、後備箱大小_small三個特徵,能夠減去一個特徵,即後備箱大小_big與後備箱大小_med都爲0則表明是後備箱大小_small。在調用pandas的get_dummies函數時,能夠添加drop_first=True參數;而使用OneHotEncoder時得本身分隔。

pd.get_dummies(df[['乘坐人數', '後備箱大小', '安全性']],drop_first=True)
乘坐人數 安全性 後備箱大小_med 後備箱大小_small
0 4 2 1 0
1 2 0 0 0
2 4 1 0 0
3 4 2 0 0
4 6 0 0 1
5 6 1 0 1
one_hot_encoder = OneHotEncoder(categories=[['med','big','small']])
one_hot_encoder.fit_transform(df[['後備箱大小']]).toarray()[:, 1:]
array([[0., 0.],
       [1., 0.],
       [1., 0.],
       [1., 0.],
       [0., 1.],
       [0., 1.]])

2.4 歸一化數據

不一樣尺度特徵能夠這樣理解,房價可能和房子面積與房間數有關而且假設兩個特徵具備相同的權重,可是現現在因爲房子面積和房間數的度量單位是不一樣的,一般狀況下房子面積的數值每每是遠大於房間數數值的,那麼稱房子面積和房間數是不一樣尺度的。若是對這兩個特徵作處理,頗有可能房子面積的影響會掩蓋住房間數對房價形成的影響。

2.4.1 最小-最大標準化

爲了解決相同權重特徵不一樣尺度的問題,可使用機器學習中的最小-最大標準化作處理,把他們兩個值壓縮在\([0-1]\)區間內。

最小-最大標準化公式:
\[ x_{norm}^{(i)}={\frac{x^{(i)}-x_{min}}{x_{max}-x_{min}}} \]
其中\(i=1,2,\cdots,m\)\(m\)爲樣本個數;\(x_{min},x_{max}\)分別是某個的特徵最小值和最大值。

# 最小最大標準化示例
from sklearn.preprocessing import MinMaxScaler
import numpy as np

test_data = np.array([1,2,3,4,5]).reshape(-1, 1).astype(float)
min_max_scaler = MinMaxScaler()
min_max_scaler.fit_transform(test_data)
array([[0.  ],
       [0.25],
       [0.5 ],
       [0.75],
       [1.  ]])

2.4.2 Z-score標準化

還有一種方法也能夠對數據進行壓縮,可是它的壓縮並不能把數據限制在某個區間,它把數據壓縮成相似高斯分佈的分佈方式,而且也能處理離羣值對數據的影響。

在分類、聚類算法中,須要使用距離來度量類似性的時候應用很是好,尤爲是數據自己呈正態分佈的時候。

數據標準化公式:
\[ x_{std}^{(i)}={\frac{x^{(i)}-\mu{_x}}{\sigma{_x}}} \]
使用標準化後,能夠把特徵列的中心設在均值爲\(0\)且標準差爲\(1\)的位置,即數據處理後特徵列符合標準正態分佈。

# Z-score標準化
from sklearn.preprocessing import StandardScaler

test_data = np.array([1,2,3,4,5]).reshape(-1, 1).astype(float)
standard_scaler = StandardScaler()
# fit_transform()=fit()+transform(), fit()方法估算平均值和方差,transform()方法對數據標準化
standard_scaler.fit_transform(test_data)
array([[-1.41421356],
       [-0.70710678],
       [ 0.        ],
       [ 0.70710678],
       [ 1.41421356]])

2.5 二值化數據

數據二值化相似於獨熱編碼,可是不一樣於獨熱編碼的是它不是0就是1,即又有點相似於二分類,不賣關子。直接給出數據二值化的公式:
\[ y = \begin{cases} 0,\quad if x \leq {\theta} \\ 1,\quad if x \geq {\theta} \end{cases} \]
上述\(\theta\)是手動設置的閾值,若是特徵值小於閾值爲0;特徵值大於閾值爲1。

# 數據二值化示例
from sklearn.preprocessing import Binarizer

test_data = np.array([1,2,3,4,5]).reshape(-1, 1).astype(float)
binarizer = Binarizer(threshold=2.5)
binarizer.fit_transform(test_data)
array([[0.],
       [0.],
       [1.],
       [1.],
       [1.]])

2.6 正則化數據

正則化是將每一個樣本縮放到單位範數,即便得每一個樣本的p範數爲1,對須要計算樣本間類似度有很大的做用,一般有L1正則化和L2正則化兩種方法。

# L1正則化示例
from sklearn.preprocessing import normalize

test_data = [[1, 2, 0, 4, 5], [2, 3, 4, 5, 9]]
normalize = normalize(test_data, norm='l1')
normalize
array([[0.08333333, 0.16666667, 0.        , 0.33333333, 0.41666667],
       [0.08695652, 0.13043478, 0.17391304, 0.2173913 , 0.39130435]])
# L2正則化示例
from sklearn.preprocessing import Normalizer

test_data = [[1, 2, 0, 4, 5], [2, 3, 4, 5, 9]]
normalize = Normalizer(norm='l2')
normalize = normalize.fit_transform(test_data)
normalize
array([[0.14744196, 0.29488391, 0.        , 0.58976782, 0.73720978],
       [0.17213259, 0.25819889, 0.34426519, 0.43033148, 0.77459667]])

2.7 生成多項式特徵

# make_circles()示例
from sklearn import datasets

X1, y1 = datasets.make_circles(
    n_samples=1000, random_state=1, factor=0.5, noise=0.1)

plt.scatter(0,0,s=23000,color='white',edgecolors='r')
plt.scatter(X1[:, 0], X1[:, 1], marker='*', c=y1)

plt.xlabel('$x_1$', fontsize=15)
plt.ylabel('$x_2$', fontsize=15)
plt.title('make_circles()', fontsize=20)
plt.show()

png

有時候可能會遇到上圖所示的數據分佈狀況,若是這個時候使用簡單的\(x_1,x_2\)特徵去擬合曲線,明顯是不可能的,可是咱們可使用,可是咱們可使用\(x_1^2+x_2^2=1\)去擬合數據,可能會獲得一個較好的模型,因此咱們有時候會對特徵作一個多項式處理,即把特徵\(x_1,x_2\)變成\(x_1^2,x_2^2\)

test_data = [[1, 2], [3, 4], [5,6]]
test_data
[[1, 2], [3, 4], [5, 6]]

經過多項式特徵,特徵將會作以下變換
\[ x_1,x_2\quad\rightarrow\quad1,x_1,x_2,x_1^2,x_1x_2,x_2^2 \]

# 生成多項式特徵示例
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly = poly.fit_transform(test_data)
poly
array([[ 1.,  1.,  2.,  1.,  2.,  4.],
       [ 1.,  3.,  4.,  9., 12., 16.],
       [ 1.,  5.,  6., 25., 30., 36.]])

3、小結

本問主要介紹了數據預處理的方法。可是現實生活中數據的數量以及複雜度遠不是本文所介紹的如此簡單,限於篇幅只能給大家作個引路人,可是對數據預處理的目標是不會變的,爲了讓算法更好的實現,實現的更好。如若只是數據中有髒數據,這些方法也夠用了。

特徵工程也算是告一段落了,可是特徵工程遠沒有這兩篇文章介紹的這麼簡單。特徵工程也如其名,工程二字就說明了太多太多,特徵工程更多的是在工做中或各種競賽中的經驗積累,他不如算法同樣有固定的套路,也沒有哪一個人能說哪一個數據處理的方法會比任何一個其餘的數據處理的方法更優。

最後,仍是送給你們那一句話:數據和特徵決定了機器學習的上限,而模型和算法只是逼近這個上限而已。

相關文章
相關標籤/搜索