Feature Scaling

โดย ชิตพงษ์ กิตตินราดร | ธันวาคม 2562

Machine learning algorithm หลายตัว เช่น SVM และ Neural Networks จะทำงานได้ดีเมื่อข้อมูล Input อยู่ใน Scale มาตรฐาน นั่นคือมีค่าเฉลี่ย Mean เท่ากับ 0 และ Variance เท่ากับ 1 ดังนั้นหากเราใช้ Algorithm เหล่านี้ หรือลองเทรนโมเดลแล้วได้ค่าความแม่นยำต่ำ หรือใช้เวลานานมากในการเทรน ให้ลองเปลี่ยน Scale ของ Feature ดู

Feature scaling มีหลายสูตร แต่สูตรที่ใช้งานได้ดีและเป็นที่นิยม คือสูตร StandardScaler ใน preprocessing module ของ scikit-learn โดย StandardScaler จะมีสูตรดังนี้:

โดย Variance () มีสูตรคือ:

ค่า x ที่ Scale แล้ว จะกระจายตัวออกรอบๆ 0 และรวมกันจะมีส่วนเบี่ยงเบนมาตรฐานเท่ากับ 1 ซึ่งเดี๋ยวจะพิสูจน์ให้ดู

เรามาลองทำ StandardScaler กัน โดยโหลดข้อมูลมาก่อน:

import numpy as np
import pandas as pd
from sklearn.preprocessing import scale
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# Load the dataset
df = pd.read_csv("data/life_expectancy.csv")

print(df.head())

ซึ่งเป็นชุดข้อมูลเดิมจากบทที่ 5 มีหน้าตาดังนี้:

   Income Happiness Area  LifeExpectancy
0  132546      High  BKK              80
1  190998    Medium   NE              75
2   49308    Medium    S              72
3   32062       Low   NE              59
4   71707      High  BKK              65

เราจะลองทำ Scaling เฉพาะ Feature "Income" และ Label "LifeExpectancy" โดยในความจริงเราไม่จำเป็นต้อง Scale label แต่จะทำให้ดูเพื่อพล็อต Scatterplot แสดงว่าความสัมพันธ์ของข้อมูลยังคงเหมือนเดิมหลังจาก Scale แล้ว

สมมุติว่าจะสร้างโมเดลเฉพาะความสัมพันธ์ระหว่าง Income กับ LifeExpectancy ลองพล็อต Scatterplot ดูดังนี้:

# Plot Income against LifeExpectancy before scaling
plt.scatter(df["Income"], df["LifeExpectancy"])

ได้ผลดังนี้:

Scatterplot of Income against LifeExpectancy before scaling

ก่อนเราจะทำการ Scale มาเตรียมข้อมูลให้เป็น numpy array กันก่อนเพื่อให้ไม่ต้องเรียกคอลัมน์จาก pandas ทุกๆ ครั้ง:

# Prep X and y
x = np.array(df["Income"]).reshape(-1,1)
y = np.array(df["LifeExpectancy"]).reshape(-1,1)
print(x.shape)
print(y.shape)

ได้มิติ x และ y เป็น (20, 1) ทั้งคู่

ต่อมาเราจะ Scale ข้อมูลสองหมวดนี้กันเลย โดยมี 2 วิธีดังนี้:

1) Function scale

เป็นวิธีที่ง่ายและสั้น ทำได้โดย:

# Method 1: Apply scaling using scale function
x_scaled = scale(x)
y_scaled = scale(y)
print(x_scaled[:5, :])
print(y_scaled[:5, :])

ได้ผลว่า:

[[ 0.47792414]
 [ 1.38439361]
 [-0.81292487]
 [-1.08037462]
 [-0.46556277]]
[[ 0.77057895]
 [ 0.35850465]
 [ 0.11126006]
 [-0.96013313]
 [-0.46564397]]

2) Class StandardScaler

วิธีนี้จะใช้ Transformer API คำนวน Mean และ Standard deviation ก่อน เพื่อนำค่าทั้งสองไปใช้ประโยชน์ภายหลังได้ เช่นการเอาไป Scale test set ให้มีลักษณะการกระจายตัวเหมือน Train set ซึ่งจะอธิบายความสำคัญของความสามารถในตอนจบบทนี้

วิธีการคือ:

# Method 2.1: Apply scaling using StandardScaler class (fit then transform)
x_scaler = StandardScaler().fit(x)
y_scaler = StandardScaler().fit(y)
print("Mean of x is:", x_scaler.mean_)
print("Variance of x is:", x_scaler.var_)
print("Standard deviation of x is:", x_scaler.scale_)
x_scaled = x_scaler.transform(x)
y_scaled = y_scaler.transform(y)
print(x_scaled[:5, :])
print(y_scaled[:5, :])

เราฟิต StandardScaler กับข้อมูลก่อน ซึ่งจะเป็นการเก็บ Parameter ที่ได้มาไว้ใน Object ก่อน เราสามารถเรียก Mean, Variance, และ Standard deviation มาดูได้โดยใช้ Method .mean_, .var_, และ .scale_ โดยได้คำตอบคือ:

Mean of x is: [1.11022302e-17]
Variance of x is: [1.]
Standard deviation of x is: [1.]

สังเกตว่า Mean เท่ากับ ซึ่งมีค่าน้อยมากๆ เข้าใกล้ 0 นั่นเอง

จากนั้นเราค่อยเอา Object ที่ฟิตแล้ว มา Transform เพื่อให้ได้ Array ที่ Scale แล้ว ได้ผลคือ:

[[ 0.47792414]
 [ 1.38439361]
 [-0.81292487]
 [-1.08037462]
 [-0.46556277]]
[[ 0.77057895]
 [ 0.35850465]
 [ 0.11126006]
 [-0.96013313]
 [-0.46564397]]

สังเกตว่าเท่ากับวิธีที่ 1

อนึ่ง เราสามารถควบ .fit และ .transform เอาไว้ในคำสั่งเดียว คือ .fit_transform ซึ่งกระชับกว่า ทำได้โดย:

# Method 2.2: Apply scaling using StandardScaler class (fit_transform)
scaler = StandardScaler()
x_scaled = scaler.fit_transform(x)
y_scaled = scaler.fit_transform(y)
print(x_scaled[:5, :])
print(y_scaled[:5, :])

จะได้ผลเหมือนเดิม

พอเรา Scale ทั้ง x และ y แล้ว ก็มาลองทำ Scatterplot ดู:

Scatterplot of Income against LifeExpectancy after scaling

พบว่าหน้าตาความสัมพันธ์และระยะห่างระหว่างข้อมูลแต่ละรายการนั้นเหมือนเดิมทุกประการ สิ่งเดียวที่ต่างออกไปคือ Scale บนแกน x และ y ที่ตอนนี้ข้อมูลจะกระจายตัวรอบๆ ค่าเฉลี่ย 0 โดยจะลองเช็คอีกทีก็ได้:

# Check that all means is 0 and std is 1.
print("All x has mean of:", x_scaled.mean(axis=0))
print("All x has standard deviation of:", x_scaled.std(axis=0))

แน่นอนว่าคำตอบคือ:

All x has mean of: [1.11022302e-17]
All x has standard deviation of: [1.]

ข้อสำคัญของการทำ Feature scaling คือต้อง Scale ทั้งข้อมูล Train set และ Test set โดยใช้ Mean และ Variance เดียวกัน เพื่อรักษารูปแบบการกระจายตัวของข้อมูลทั้งสองชุดให้เหมือนกัน อย่างไรก็ตาม ไม่ควร Scale ข้อมูลทั้งหมดทีเดียวและค่อยแยก Train set กับ Test set เพราะการทำอย่างนั้นจะทำให้ข้อมูลการกระจายตัวของ Test set "รั่ว" ไปส่งผลต่อการ Scale train set

ดังนั้น วิธีที่ถูกต้องคือ:

  1. ใช้ StandardScaler เรียก .fit เข้ากับ X_train แล้วผลที่ได้ไว้เป็น Instance object
  2. เรียก .transform จาก Instance ในข้อ 1 โดยใส่ Argument เป็น X_train เพื่อเปลี่ยนชุด X_train ให้ได้ Scale
  3. จากนั้นเรียก .transform จาก Instance ในข้อ 1 โดยใส่ Argument เป็น X_test ซึ่งเป็นการเรียก Parameter (Mean, variance) ที่เคยฟิตจาก X_train มาใช้กับ X_test

การทำ Feature scaling นั้นยังมีวิธีอื่นๆ อีก เช่น:

แนะนำให้ศึกษาเพิ่มจากคู่มือของ scikit-learn และแหล่งข้อมูลอื่นๆ ได้เอง

บทต่อไปจะเข้าสู่รายละเอียดของการเทรนโมเดล โดยจะพูดถึงปัญหา Bias และ Variance และวิธีแก้

หน้าแรก | บทที่ 5 Categorical Encoding | บทที่ 7 Bias and Variance

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.