特徵工程(二)數據轉換

數據科學項目中少不了要用到機器學習算法。通常每種算法都會對數據有相應的要求,比如有的算法要求數據集特徵是離散的,有的算法要求數據集特徵是分類型的,而數據集特徵不一定就滿足這些要求,必須依據某些原則、方法對數據進行變換。

特徵變換

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'])

image-20220609160713777

項目案例

對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()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章