數據科學項目中少不了要用到機器學習算法。通常每種算法都會對數據有相應的要求,比如有的算法要求數據集特徵是離散的,有的算法要求數據集特徵是分類型的,而數據集特徵不一定就滿足這些要求,必須依據某些原則、方法對數據進行變換。
2.1 特徵的類型
特徵的類型由其所有值的集合決定,通常有如下幾種:
- 分類型:性別分男女,職業分士農工商
- 二值型:0和1
- 順序型:職稱有:講師、副教授、教授
- 數值型:整數、浮點數
2.2 特徵數值化
基礎知識
以基因測序預測病患實例:
import pandas as pd
df = pd.DataFrame({"gene_segA": [1, 0, 0, 1, 1, 1, 0, 0, 1, 0],
"gene_segB": [1, 0, 1, 0, 1, 1, 0, 0, 1, 0],
"hypertension": ["Y", 'N', 'N', 'N', 'N', 'N', 'Y', 'N', 'Y', 'N'],
"Gallstones": ['Y', 'N', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'N', 'Y']
})
df
gene_segA | gene_segB | hypertension | Gallstones | |
---|---|---|---|---|
0 | 1 | 1 | Y | Y |
1 | 0 | 0 | N | N |
2 | 0 | 1 | N | N |
3 | 1 | 0 | N | N |
4 | 1 | 1 | N | Y |
5 | 1 | 1 | N | Y |
6 | 0 | 0 | Y | Y |
7 | 0 | 0 | N | N |
8 | 1 | 1 | Y | N |
9 | 0 | 0 | N | Y |
如果機器學習算法中使用此數據,是無法訓練模型的,因爲算法不能理解Y和N這樣的字符串。
將數據集中的Y、N替換爲數字,比如用1替換Y,用0替換N。
df.replace({"N": 0, 'Y': 1})
在scikit-learn中也提供了專用模塊
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit_transform(df['hypertension'])
array([1, 0, 0, 0, 0, 0, 1, 0, 1, 0])
實例化LabelEncoder,得到了一個實現特徵數值化的模型實例,用它訓練特徵中的數據,即可得到其中的枚舉值。hypertension是分類型或者二值型,le實例能自動從0開始,將每個值用整數替換。
le.fit_transform([1, 3, 3, 7, 3, 1])
array([0, 1, 1, 2, 1, 0], dtype=int64)
LabelEncoder實例對象還有一個實現“反向取值”的方法。
le.inverse_transform([0, 1, 1, 2, 1, 0])
array([1, 3, 3, 7, 3, 1])
項目案例
假設有數據['white', 'green', 'red', 'green', 'white'],要求利用此數據創建特徵數值化模型,然後用模型對另外數據集進行特徵變換。
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit(['white', 'green', 'red', 'green', 'white'])
le.classes_
array(['green', 'red', 'white'], dtype='<U5')
le.transform(["green", 'green', 'green', 'white'])
array([0, 0, 0, 2])
但如果出現了超出所得分類的參數,就會報錯。
le.transform(["green", 'green', 'green', 'white']) #報錯
2.3 特徵二值化
無論是連續型特徵還是離散型特徵,都可以進行二值化轉換
基礎知識
import pandas as pd
pm25 = pd.read_csv("datasets/pm2.csv")
pm25.head()
RANK | CITY_ID | CITY_NAME | Exposed days | |
---|---|---|---|---|
0 | 1 | 594 | 拉薩 | 2 |
1 | 2 | 579 | 玉溪 | 7 |
2 | 3 | 263 | 廈門 | 8 |
3 | 4 | 267 | 泉州 | 9 |
4 | 5 | 271 | 漳州 | 10 |
下面以平均值爲閾值,對特徵Exposed days進行二值化
import numpy as np
pm25['bdays'] = np.where(pm25["Exposed days"] > pm25["Exposed days"].mean(), 1, 0)
pm25.sample(10)
RANK | CITY_ID | CITY_NAME | Exposed days | bdays | |
---|---|---|---|---|---|
3 | 4 | 267 | 泉州 | 9 | 0 |
243 | 266 | 350 | 濱州 | 203 | 1 |
252 | 275 | 367 | 新鄉 | 216 | 1 |
11 | 12 | 64 | 鄂爾多斯 | 18 | 0 |
229 | 252 | 419 | 隨州 | 186 | 1 |
196 | 219 | 324 | 濰坊 | 144 | 1 |
127 | 139 | 616 | 平涼 | 96 | 0 |
221 | 244 | 238 | 合肥 | 175 | 1 |
123 | 135 | 128 | 白城 | 95 | 0 |
182 | 205 | 451 | 婁底 | 132 | 1 |
新增特徵bdays是對Exposed days二值化後所得到的二值型特徵。 除了用np.where函數實現特徵二值化,還可以使用scikit-learn提供的二值化模塊Binarizer實現特徵二值化。
from sklearn.preprocessing import Binarizer
bn = Binarizer(threshold=pm25["Exposed days"].mean())
result = bn.fit_transform(pm25[["Exposed days"]])
pm25['sk-bdays'] = result
pm25.sample(10)
項目案例
對讀入的圖像數據進行二值化變換
![cat](C:/Users/10325/Desktop/cat.png)%matplotlib inline
import matplotlib.pyplot as plt
import cv2
# 寫一個專門在Jupyter中顯示圖片的函數
def show_img(img):
if len(img.shape) == 3:
b, g, r = cv2.split(img)
img = cv2.merge([r, g, b])
plt.imshow(img)
else:
plt.imshow(img, cmap="gray")
plt.axis("off")
plt.show()
cat = cv2.imread("datasets/cat.png")
show_img(cat)
圖像的二值化就是將圖像中的內容分爲兩部分:前景和背景。要設置一個閾值,每個像素的值與閾值比較,以確定是前景還是背景。
先把得到的圖像進行灰度化處理
gray_cat = cv2.cvtColor(cat, cv2.COLOR_BGR2GRAY)
show_img(gray_cat)
實施二值化操作
超過閾值127的像素就設置爲最大值255,否則就是0
ret,thr = cv2.threshold(gray_cat, 127, 255, cv2.THRESH_BINARY)
show_img(thr)
2.4 OneHot編碼
基礎知識
import pandas as pd
g = pd.DataFrame({"gender": ["man", 'woman', 'woman', 'man', 'woman']})
g
gender | |
---|---|
0 | man |
1 | woman |
2 | woman |
3 | man |
4 | woman |
特徵gender的值除了man就是woman。在2.2中用數值化的方式處理這種類型的特徵,但數值化會帶來原來沒有的“大小關係”。爲避免這種“副作用”,下面換一種處理方式。
pd.get_dummies(g)
gender_man | gender_woman | |
---|---|---|
0 | 1 | 0 |
1 | 0 | 1 |
2 | 0 | 1 |
3 | 1 | 0 |
4 | 0 | 1 |
get_dummies作用是將分類特徵轉化爲虛擬變量(啞變量)
persons = pd.DataFrame({"name":["Newton", "Andrew Ng", "Jodan", "Bill Gates"], 'color':['white', 'yellow', 'black', 'white']})
persons
name | color | |
---|---|---|
0 | Newton | white |
1 | Andrew Ng | yellow |
2 | Jodan | black |
3 | Bill Gates | white |
此處color有三個值,是分類特徵,但不是二值型的了。使用OneHot編碼,分別以三個值爲特徵名稱,當第0行中white爲1(稱爲高位,且只有一個高位)時,則其他特徵的值都是低位(記爲0)
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder()
features = ohe.fit_transform(persons[['color']])
features.toarray()
array([[0., 1., 0.],[0., 0., 1.], [1., 0., 0.],[0., 1., 0.]])
項目案例
創建如下數據
df = pd.DataFrame({
"color": ['green', 'red', 'blue', 'red'],
"size": ['M', 'L', 'XL', 'L'],
"price": [29.9, 69.9, 99.9, 59.9],
"classlabel": ['class1', 'class2', 'class1', 'class1']
})
df
color | size | price | classlabel | |
---|---|---|---|---|
0 | green | M | 29.9 | class1 |
1 | red | L | 69.9 | class2 |
2 | blue | XL | 99.9 | class1 |
3 | red | L | 59.9 | class1 |
要求對此數據集完成如下操作:
- 對有必要的特徵進行數值化操作
- 對有必要的特徵進行OneHot編碼
將特徵size數值化
size_mapping = {'XL': 3, 'L': 2, 'M': 1}
df['size'] = df['size'].map(size_mapping)
df
color | size | price | classlabel | |
---|---|---|---|---|
0 | green | 1 | 29.9 | class1 |
1 | red | 2 | 69.9 | class2 |
2 | blue | 3 | 99.9 | class1 |
3 | red | 2 | 59.9 | class1 |
將特徵color進行OneHot編碼
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder()
fs = ohe.fit_transform(df[['color']])
fs_ohe = pd.DataFrame(fs.toarray(),columns=['color_blue', 'color_green','color_red'])
df = pd.concat([df, fs_ohe], axis=1)
df
color | size | price | classlabel | color_blue | color_green | color_red | |
---|---|---|---|---|---|---|---|
0 | green | 1 | 29.9 | class1 | 0.0 | 1.0 | 0.0 |
1 | red | 2 | 69.9 | class2 | 0.0 | 0.0 | 1.0 |
2 | blue | 3 | 99.9 | class1 | 1.0 | 0.0 | 0.0 |
3 | red | 2 | 59.9 | class1 | 0.0 | 0.0 | 1.0 |
2.5 數據變換
基礎知識
爲了研究數據集中特徵之間潛在的規律,有時候還需要對特徵運用某些函數進行變換,以便更容易地找到其中的規律。
import pandas as pd
data = pd.read_csv("datasets/freefall.csv", index_col=0)
data.describe()
time | location | |
---|---|---|
count | 100.000000 | 1.000000e+02 |
mean | 250.000000 | 4.103956e+05 |
std | 146.522832 | 3.709840e+05 |
min | 0.000000 | 0.000000e+00 |
25% | 124.997500 | 7.658593e+04 |
50% | 250.000000 | 3.062812e+05 |
75% | 375.002500 | 6.890859e+05 |
max | 500.000000 | 1.225000e+06 |
這個數據集記錄了物體從足夠高的位置開始下落,以及不同時刻所對應的下落高度、 我們的目的是找到時間和下落高度兩個變量之間的函數關係(假裝不知道自由落體運動函數式)
%matplotlib inline
import seaborn as sns
ax = sns.scatterplot(x='time', y='location', data=data)
對time和location特徵進行對數變換,用變換後的數據繪製散點圖
import numpy as np
data.drop([0], inplace=True) # 去掉0,不計算log0
data['logtime'] = np.log10(data['time'])
data['logloc'] = np.log10(data['location'])
data.head()
time | location | logtime | logloc | |
---|---|---|---|---|
1 | 5.05 | 124.99 | 0.703291 | 2.096875 |
2 | 10.10 | 499.95 | 1.004321 | 2.698927 |
3 | 15.15 | 1124.89 | 1.180413 | 3.051110 |
4 | 20.20 | 1999.80 | 1.305351 | 3.300987 |
5 | 25.25 | 3124.68 | 1.402261 | 3.494806 |
ax2 = sns.scatterplot(x='logtime', y='logloc', data=data)
根據輸出結果,可以判定變換之後得到特徵logtime和logloc之間是直線關係
from sklearn.linear_model import LinearRegression
reg = LinearRegression()
reg.fit(data['logtime'].values.reshape(-1, 1), data['logloc'].values.reshape(-1, 1))
(reg.coef_, reg.intercept_)
(array([[1.99996182]]), array([0.69028797]))
引入scikit-learn的線性迴歸模型,並用上述變換後的數據對這個模型進行訓練,得到直線的斜率是2,截距是0.69 表達式如下
\(𝑙𝑔𝐿=2𝑙𝑔𝑡+0.69\)
\(𝐿 = 4.9𝑡^2\)
符合自由落體運動定律
具體如何進行數據變換?應選擇什麼樣函數?通常需要根據數據和業務特點而定,以上所實行的是對數變換,此外常用的還有指數變換、多項式變換、Box-Cox變換等。
項目案例
dc_data = pd.read_csv('datasets/sample_data.csv')
dc_data.head()
MONTH | AIR_TIME | |
---|---|---|
0 | 1 | 28 |
1 | 1 | 29 |
2 | 1 | 29 |
3 | 1 | 29 |
4 | 1 | 29 |
再對其進行數據處理之前,先觀察它的分佈
%matplotlib inline
import matplotlib.pyplot as plt
h = plt.hist(dc_data['AIR_TIME'], bins=100)
dc_data['AIR_TIME']中的數據很顯然不是標準正態分佈,下面就對它進行變換
from sklearn.preprocessing import power_transform
dft2 = power_transform(dc_data[['AIR_TIME']], method='box-cox')
hbcs = plt.hist(dft2, bins=100)
Box-Cox屬於廣義冪等變換,sklearn.preprocessing中的power_transform函數的命名也符合這種說法。
利用power_transform函數除了可以實現Box-Cox變換,還可以實現Yeo-Johnson變換。
2.6 特徵離散化
- 離散型:在任意兩個值之間具有可計數的值
- 連續型:在任意兩個值之間具有無限個值 機器學習中的一些算法,比如決策樹、樸素貝葉斯、對數概率迴歸等算法,都要求變量必須是離散化。
此外對於連續型特徵,在離散化之後,能夠降低對離羣數據的影響,例如將表示年齡的特徵離散化,大於50的是1,否則爲0。如果此特徵中出現了年齡爲500的離羣值,在離散化後,該離羣值對特徵的影響就被消除了。相對於連續型特徵,離散型特徵在計算速度、表達能力、模型穩定性等方面都具有優勢。
通常使用的離散化方法可以劃分爲“有監督的”和“無監督的”兩類
離散化也可以稱爲“分箱”
無監督離散化
基礎知識
import pandas as pd
ages = pd.DataFrame({'years':[10, 14, 30, 53, 67, 32, 45], 'name':['A', 'B', 'C', 'D', 'E', 'F', 'G']})
ages
years | name | |
---|---|---|
0 | 10 | A |
1 | 14 | B |
2 | 30 | C |
3 | 53 | D |
4 | 67 | E |
5 | 32 | F |
6 | 45 | G |
如果對特徵years離散化,可以使用Pandas提供的函數cut
pd.cut(ages['years'],3)
0 (9.943, 29.0]
1 (9.943, 29.0]
2 (29.0, 48.0]
3 (48.0, 67.0]
4 (48.0, 67.0]
5 (29.0, 48.0]
6 (29.0, 48.0]
Name: years, dtype: category
Categories (3, interval[float64]): [(9.943, 29.0] < (29.0, 48.0] < (48.0, 67.0]]
cut函數的第2個參數3,表示將ages['years']劃分爲等寬的3個區間[(9.943, 29.0] < (29.0, 48.0] < (48.0, 67.0]
因爲離散化的別稱是“分箱”,所以上述操作也稱爲“等寬分箱法”
但是,若使用等寬劃分,在遇到離羣值時常會出現問題
ages2 = pd.DataFrame({'years':[10, 14, 30, 53, 300, 32, 45], 'name':['A', 'B', 'C', 'D', 'E', 'F', 'G']})
klass2 = pd.cut(ages2['years'], 3, labels=['Young', 'Middle', 'Senior']) # ②
ages2['label'] = klass2
ages2
years | name | label | |
---|---|---|---|
0 | 10 | A | Young |
1 | 14 | B | Young |
2 | 30 | C | Young |
3 | 53 | D | Young |
4 | 300 | E | Senior |
5 | 32 | F | Young |
6 | 45 | G | Young |
Young第4個樣本的離羣值導致其他記錄都被標記爲Young。
這裏對離羣值的處理通過指定數據的分割點,避免了離羣值的影響。
ages2 = pd.DataFrame({'years':[10, 14, 30, 53, 300, 32, 45], 'name':['A', 'B', 'C', 'D', 'E', 'F', 'G']})
klass2 = pd.cut(ages2['years'], bins=[9, 30, 50, 300], labels=['Young', 'Middle', 'Senior']) # ③
ages2['label'] = klass2
ages2
years | name | label | |
---|---|---|---|
0 | 10 | A | Young |
1 | 14 | B | Young |
2 | 30 | C | Young |
3 | 53 | D | Young |
4 | 300 | E | Senior |
5 | 32 | F | Young |
6 | 45 | G | Young |
在sklearn中有實現無監督離散化的類KBinsDiscretizer
from sklearn.preprocessing import KBinsDiscretizer
kbd = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
trans = kbd.fit_transform(ages[['years']])
ages['kbd'] = trans[:, 0]
ages
years | name | kbd | |
---|---|---|---|
0 | 10 | A | 0.0 |
1 | 14 | B | 0.0 |
2 | 30 | C | 1.0 |
3 | 53 | D | 2.0 |
4 | 67 | E | 2.0 |
5 | 32 | F | 1.0 |
6 | 45 | G | 1.0 |
KBinsDiscretizer(
n_bins=5,
*,
encode='onehot',
strategy='quantile',
dtype=None,
)
Parameters
----------
n_bins : int or array-like of shape (n_features,), default=5
The number of bins to produce. Raises ValueError if ``n_bins < 2``.
encode : {'onehot', 'onehot-dense', 'ordinal'}, default='onehot'
Method used to encode the transformed result.
- 'onehot': Encode the transformed result with one-hot encoding
and return a sparse matrix. Ignored features are always
stacked to the right.
- 'onehot-dense': Encode the transformed result with one-hot encoding
and return a dense array. Ignored features are always
stacked to the right.
- 'ordinal': Return the bin identifier encoded as an integer value.
strategy : {'uniform', 'quantile', 'kmeans'}, default='quantile'
Strategy used to define the widths of the bins.
- 'uniform': All bins in each feature have identical widths.
- 'quantile': All bins in each feature have the same number of points.
- 'kmeans': Values in each bin have the same nearest center of a 1D
k-means cluster.
dtype : {np.float32, np.float64}, default=None
The desired data-type for the output. If None, output dtype is
consistent with input dtype. Only np.float32 and np.float64 are
supported.
KBinsDiscretizer的參數strategy有三個取值,代表了無監督離散化的三個常用方法。
- ‘uniform’:統一,離散化在每個特徵上都是統一的,這意味着bin寬度在每個維度上都是恆定的。
- ‘quantile’:離散化是在量化後的值上完成的,這意味着每個單元格具有大約相同數量的樣本。
- ‘kmeans’:離散化基於KMeans聚類過程的質心。
項目案例
鳶尾花數據集的各個特徵是連續值,要求用此數據集訓練機器學習的分類算法,並比較在離散化與原始值兩種狀態下的分類效果。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.preprocessing import KBinsDiscretizer
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
iris = load_iris()
鳶尾花數據集有4個特徵,用如下方式顯示:
iris.feature_names
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
爲簡化問題,在下面的操作中只選用兩個特徵:
X = iris.data
y = iris.target
X = X[:, [2, 3]]
先直觀地顯示這些數據的分佈。
%matplotlib inline
import matplotlib.pyplot as plt
# X[:, 0]是第一個特徵的所有數據,X[:, 1]是第二個特徵的所有數據
plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.3, cmap=plt.cm.RdYlBu, edgecolor='black')
然後,對這些數據離散化,並用可視化的方式顯示離散化後的數據分佈
Xd = KBinsDiscretizer(n_bins=10, encode='ordinal', strategy='uniform').fit_transform(X)
plt.scatter(Xd[:, 0], Xd[:, 1], c=y, cmap=plt.cm.RdYlBu, edgecolor='black')
離散化後的數據更涇渭分明,有利於分類算法的應用。
下面將以上兩種數據用於決策樹分類算法,比較優劣。
dtc = DecisionTreeClassifier(random_state=0)
score1 = cross_val_score(dtc, X, y, cv=5)
score2 = cross_val_score(dtc, Xd, y, cv=5)
np.mean(score1), np.std(score1)
(0.9466666666666667, 0.039999999999999994)
np.mean(score2), np.std(score2)
(0.96, 0.03265986323710903)
從計算後的平均值和標準差,可以看出,在實施離散化後,對優化模型的性能還是有價值的。
如果使用k-means聚類方法進行離散化,效果還會更好。
km = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='kmeans').fit_transform(X)
s = cross_val_score(dtc, km, y, cv=5)
np.mean(s), np.std(s)
(0.9733333333333334, 0.02494438257849294)
有監督離散化
所謂有監督離散化,類似於有監督學習,需要根據樣本標籤實現離散化
此處介紹基於熵和信息增益的有監督離散化,以下表爲例,依據results列對values列的數值實現離散化,results列就是所謂的標籤。
values | results |
---|---|
1 | Y |
1 | Y |
2 | N |
3 | Y |
3 | N |
表中 results中的值爲Y的樣本數爲3;值爲N的樣本數是2
根據熵計算公式
\(Entropy = -\sum_{i=0}^m p_i log_2 p_i\) 得
\(E(R) = -\frac{3}{5} log_2 \frac{3}{5} - \frac{2}{5}log_3 \frac{3}{5} = 0.97\)
如果將特徵values的值以整數2爲離散化的分割點,分別統計results的值爲Y和N的數據。
統計方法 | Y | N | 總計 |
---|---|---|---|
小於或等於2的樣本數量 | 2 | 1 | 3 |
大於2的樣本數量 | 1 | 1 | 2 |
再計算熵
$E(R,V) = \frac{3}{5}E(2,1)+ \frac{2}{5}E(1,1) = 0.95 \( 然後計算信息增益,\)G = E(R) - E(R,v) = 0.02$
用同樣的方法,如果以1爲values離散化的分割點,其熵爲0.55,相應的信息增益爲0.42
顯然,以1爲分割點,信息增益大,那麼特徵values的值離散化之後爲[0,0,1,1,1]
上述過程用程序來實現,可以利用entropy_based_binning的計算模塊 pip install entropy_based_binning
import entropy_based_binning as ebb
A = np.array([[1,1,2,3,3], [1,1,0,1,0]])
ebb.bin_array(A, nbins=2, axis=1)
array([[0, 0, 1, 1, 1],[1, 1, 0, 1, 0]])
2.7 數據規範化
規範化包含對特徵的標準化、區間化和歸一化等操作。
標準化
標準化計算公式
\(x_{std}^{(i)} = \frac{x^{(i)}- μ_x}{σ_x}\)
使用鳶尾花書籍,用StandardScaler創建標準化實例,將數據標準化
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
iris = datasets.load_iris()
iris_std = StandardScaler().fit_transform(iris.data)
# 原有的前5個樣本的數值
iris['data'][:5]
array([[5.1, 3.5, 1.4, 0.2],
[4.9, 3. , 1.4, 0.2],
[4.7, 3.2, 1.3, 0.2],
[4.6, 3.1, 1.5, 0.2],
[5. , 3.6, 1.4, 0.2]])
# 這5個樣本經過標準化變換之後的Z分數
iris_std[:5]
array([[-0.90068117, 1.01900435, -1.34022653, -1.3154443 ],
[-1.14301691, -0.13197948, -1.34022653, -1.3154443 ],
[-1.38535265, 0.32841405, -1.39706395, -1.3154443 ],
[-1.50652052, 0.09821729, -1.2833891 , -1.3154443 ],
[-1.02184904, 1.24920112, -1.34022653, -1.3154443 ]])
import numpy as np
np.mean(iris_std, axis=0)
array([-1.69031455e-15, -1.84297022e-15, -1.69864123e-15, -1.40924309e-15])
np.std(iris_std, axis=0)
array([1., 1., 1., 1.])
經標準化變換之後的數據集,各個特徵的平均值爲0,標準差爲1。適合多數機器學習算法,比如對數概率迴歸和SVM等。
區間化
區間化計算公式
\(x_{scaled}^{(i)} = \frac{x^{(i)}- x_{min}}{x_{(max)}- x_{min}}\)
與標準化類似,在sklearn中由MinMaxScaler實現上述計算
from sklearn.preprocessing import MinMaxScaler
iris_mm = MinMaxScaler().fit_transform(iris.data)
iris_mm[:5]
array([[0.22222222, 0.625 , 0.06779661, 0.04166667],
[0.16666667, 0.41666667, 0.06779661, 0.04166667],
[0.11111111, 0.5 , 0.05084746, 0.04166667],
[0.08333333, 0.45833333, 0.08474576, 0.04166667],
[0.19444444, 0.66666667, 0.06779661, 0.04166667]])
np.mean(iris_mm, axis=0)
array([0.4287037 , 0.44055556, 0.46745763, 0.45805556])
np.std(iris_mm, axis=0)
array([0.22925036, 0.18100457, 0.29820408, 0.31653859])
在sklearn中還有一個MinMaxScaler功能類似的類RobustScaler,從名稱上來看,這個類應該有魯棒性。
此類特徵縮放所執行的數學公式是:
\(x_{nor}^{(i)} = \frac{x^{(i)}- Q_{1}(x)}{Q_3(x)- Q_1(x)}\)
import pandas as pd
X = pd.DataFrame({
'x1': np.concatenate([np.random.normal(20, 1, 1000), np.random.normal(1, 1, 25)]),
'x2': np.concatenate([np.random.normal(30, 1, 1000), np.random.normal(50, 1, 25)]),
})
X.sample(10)
x1 | x2 | |
---|---|---|
1008 | -0.031074 | 50.628570 |
713 | 20.951094 | 29.837530 |
87 | 19.292356 | 30.371313 |
847 | 21.128625 | 29.077542 |
208 | 20.384260 | 30.683392 |
603 | 20.620852 | 30.198629 |
763 | 18.410347 | 31.632454 |
901 | 21.100977 | 30.285051 |
370 | 21.244946 | 28.534997 |
939 | 20.800052 | 29.687268 |
這裏創建了數據集X,它有兩個特徵,相對於特徵x1而言,特徵x2的數據有更大的數據變換範圍,方差較大。
np.std(X, axis=0)
x1 3.110426
x2 3.236057
dtype: float64
對X數據集分別用類MinMaxScaler和RobustScaler進行區間化
from sklearn.preprocessing import RobustScaler, MinMaxScaler
robust = RobustScaler()
robust_scaled = robust.fit_transform(X)
robust_scaled = pd.DataFrame(robust_scaled, columns=['x1', 'x2'])
minmax = MinMaxScaler()
minmax_scaled = minmax.fit_transform(X)
minmax_scaled = pd.DataFrame(minmax_scaled, columns=['x1', 'x2'])
爲直觀地比較縮放的效果,再分別對三種數據以可視化的方式表示它們的分佈。
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(9, 5))
ax1.set_title('Before Scaling')
sns.kdeplot(X['x1'], ax=ax1)
sns.kdeplot(X['x2'], ax=ax1)
ax2.set_title('After Robust Scaling')
sns.kdeplot(robust_scaled['x1'], ax=ax2)
sns.kdeplot(robust_scaled['x2'], ax=ax2)
ax3.set_title('After Min-Max Scaling')
sns.kdeplot(minmax_scaled['x1'], ax=ax3)
sns.kdeplot(minmax_scaled['x2'], ax=ax3)
歸一化
from sklearn.preprocessing import Normalizer
# 默認按l2範數歸一化
norma = Normalizer()
norma.fit_transform([[3, 4]])
array([[0.6, 0.8]])
計算公式
\(\frac{3}{ \sqrt{3^2 +4^2}}= 0.6 ,\frac{4}{ \sqrt{3^2 +4^2}}= 0.8\)
# 按l1範數歸一化
norma1 = Normalizer(norm='l1')
norma1.fit_transform([[3, 4]])
計算公式
\(\frac{3}{|3|+|4|}= 0.42857 ,\frac{4}{ |3|+|4|}= 0.57143\)
除了上述兩種,norm也可以設置爲max,其含義是依據向量中的最大值進行歸一化
norma_max = Normalizer(norm='max')
norma_max.fit_transform([[3, 4]])
array([[0.75, 1. ]])
這裏注意,使用Normalizer實施的歸一化與MinMaxScaler所實施的(0,1)區間化,雖然都是將數值經過縮放變換到0到1的範圍,但兩者還是有很大差別的,下面的示例就展示了其中的差別。
from mpl_toolkits.mplot3d import Axes3D
df = pd.DataFrame({
'x1': np.random.randint(-100, 100, 1000).astype(float),
'y1': np.random.randint(-80, 80, 1000).astype(float),
'z1': np.random.randint(-150, 150, 1000).astype(float),
})
scaler = Normalizer()
scaled_df = scaler.fit_transform(df)
scaled_df = pd.DataFrame(scaled_df, columns=df.columns)
fig = plt.figure(figsize=(9, 5))
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')
ax1.scatter(df['x1'], df['y1'], df['z1'])
ax2.scatter(scaled_df['x1'], scaled_df['y1'], scaled_df['z1'])
在創建的數據集中有三個特徵,本來在三維空間內分佈的數據,經過歸一化(l2範數)之後,將所有點都集中到一個球範圍內。
如果利用MinMaxScaler將這些數據區間化(0,1)範圍,會怎麼樣?
scaler = MinMaxScaler()
scaled_df = scaler.fit_transform(df)
scaled_df = pd.DataFrame(scaled_df, columns=df.columns)
fig = plt.figure(figsize=(9, 5))
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')
ax1.scatter(df['x1'], df['y1'], df['z1'])
ax2.scatter(scaled_df['x1'], scaled_df['y1'], scaled_df['z1'])
項目案例
對wine_data.csv中的特徵Alcohol、Malic_acid的數據進行標準化和最大最小區間化操作,然後用圖示的方式比較原始數據和規範化後的數據分佈。
import pandas as pd
import numpy as np
df = pd.read_csv("datasets/wine_data.csv",usecols=[0,1,2])
df.head()
Class_label | Alcohol | Malic_acid | |
---|---|---|---|
0 | 1 | 14.23 | 1.71 |
1 | 1 | 13.20 | 1.78 |
2 | 1 | 13.16 | 2.36 |
3 | 1 | 14.37 | 1.95 |
4 | 1 | 13.24 | 2.59 |
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
std_scaler = StandardScaler()
df_std = std_scaler.fit_transform(df[['Alcohol', 'Malic_acid']])
mm_scaler = MinMaxScaler()
df_mm = mm_scaler.fit_transform(df[['Alcohol', 'Malic_acid']])
分別繪製特徵Alcohol和Malic_acid的原始數據、標準化數據、區間化數據的散點圖
%matplotlib inline
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 6))
plt.scatter(df['Alcohol'], df['Malic_acid'],
color='green', label='input scale', alpha=0.5) # ③
plt.scatter(df_std[:,0], df_std[:,1], color='black',
label='Standardized', alpha=0.3) # ④
plt.scatter(df_mm[:,0], df_mm[:,1],
color='blue', label='min-max scaled', alpha=0.3) # ⑤
plt.title('Alcohol and Malic Acid content of the wine dataset')
plt.xlabel('Alcohol')
plt.ylabel('Malic Acid')
plt.legend(loc='upper left')
plt.grid()
plt.tight_layout()