Bài thứ 15 trong chuỗi các bài viết về chủ đề Data Preparation cho các mô hình ML và là bài thứ 2 về về Data Transforms. Trong bài này, chúng ta sẽ tìm hiểu cách thức thực hiện Transforms cho features dạng categorical. Chúng ta cũng sẽ thực hành trên bộ dữ liệu thực tế.
Kiểu dữ liệu categorical có thể được chia thành 2 loại:
Nominal: Các features bao gồm một số hữu hạn các giá trị không có sự liên quan thứ tự với nhau. VD: Feature Animals bao gồm các giá trị: Dog, Cat, Rabit, Chicken, …
Ordinal: Các features bao gồm một số hữu hạn các giá trị có sự liên hệ thứ tự với nhau. VD: Feature Rank bao gồm các giá trị: First, Second, Third, …
Đa số các thuật toán ML đều không thể làm việc trực tiếp với dữ liệu dạng categorical (trừ Decision Tree). Chúng ta phải chuyển các giá trị của features từ categorical sang numerical trước khi đưa vào huấn luyện các mô hình. Quá trình chuyển đổi này được gọi là Encoding.
Có 3 kỹ thuật phổ biến để thực hiện encoding categorical data, đó là:
Ordinal Encoding quy định mỗi giá trị duy nhất của Feature là 1 số tự nhiên, bắt đầu từ số 0. VD: Feature Colors có các giá trị: Red, Green, Yellow thì Red:0, Green:1, Yellow:2. Bởi vì các số tự nhiên, bản chất đã có sự liên quan về thứ tự nên cách này chỉ phù hợp cho dữ liệu categorical dạng ordinal.
Trong scikit-learn, Ordinal Encoding được thực hiện bởi lớp OrdinalEncoder(). Cách sử dụng như ví dụ sau:
# example of a ordinal encoding
from numpy import asarray
from sklearn.preprocessing import OrdinalEncoder
# define data
data = asarray([['red'], ['green'], ['blue']])
print(data)
# define ordinal encoding
encoder = OrdinalEncoder()
# transform data
result = encoder.fit_transform(data)
print(result)
Kết quả thực hiện:
[['red']
['green']
['blue']]
[[2.]
[1.]
[0.]]
Ordinal Encoding chỉ áp dụng cho các features, đối với target, ta sử dụng LabelEncoder(). Cách làm việc của 2 lớp này hoàn toàn tương tự nhau.
One Hot Encoding áp dụng cho các features dạng categorical là nominal. Mỗi giá trị của feature được chuyển thành 1 giá trị nhị phân (bao gồm các con số 0 và 1) mà ở đó, số lượng các số 0 và 1 bằng số lượng giá trị duy nhất của feature, số 1 chỉ xuất hiện tại một vị trí trong mảng đại diện cho giá trị duy nhất của feature đó, các vị trí khác trong số nhị phân mang giá trị 0.
Xem ví dụ dưới đây:
# example of a one hot encoding
from numpy import asarray
from sklearn.preprocessing import OneHotEncoder
# define data
data = asarray([['red'], ['green'], ['blue']])
print(data)
# define one hot encoding
encoder = OneHotEncoder(sparse=False)
# transform data
onehot = encoder.fit_transform(data)
print(onehot)
Kết quả thực hiện:
[['red']
['green']
['blue']]
[[0. 0. 1.]
[0. 1. 0.]
[1. 0. 0.]]
Ta thấy [0. 0. 1] đại diện cho red, [0. 1. 0.] đại diên cho green, còn [1. 0. 0.] đại diện cho blue. Nếu biết trước danh sách các giá trị duy nhất của feature, ta có thể thông báo cho OneHotEncoder() thông qua tham số categories. Nếu không được chỉ rõ thì mặc đinh, categories sẽ lấy theo tập dữ liệu mà nó được fit. Và về sau, nếu gặp giá trị khác trong tập fit đó thì OneHotEncoder() sẽ trả về lỗi xử lý.
One Hot Encoding tạo ra một số nhị phân đại diện cho mỗi giá trị duy nhất của feature. Tuy nhiên, dễ nhận thấy rằng cách biểu diễn này có một chút dư thừa.
VD: Giả sử feature của ta có 3 giá trị duy nhất là A,B, C. Nếu ta biết [1,0,0] là A, [0,1,0] là B thì giá trị còn lại [0,0,1] chắc chắn là C. Vậy thì ta có thể không cần một số nhị phân để biểu diễn C nữa. Tổng quát ra thì nếu ta có N giá trị duy nhất của feature thì ta chỉ cần N-1 giá trị nhị phân để biểu diễn cho feature đó.
Giữ nguyên ý tưởng đó nhưng thay đổi cách làm một chút. Ta vẫn giữ nguyên N giá trị nhị phân để biểu diễn N giá trị duy nhất của feature, nhưng chiều dài của mỗi giá trị nhị phân đó thì giảm đi 1. Cụ thể: A -> [1,0], B -> [0,1], C -> [0,0].
Để áp dụng Dummy Variable Encoding trong scikit-learn, chúng ta vẫn sử dụng lớp OneHotEncoder() và set giá trị cho tham số drop bằng first.
Ví dụ:
# example of a dummy variable encoding
from numpy import asarray
from sklearn.preprocessing import OneHotEncoder
# define data
data = asarray([['red'], ['green'], ['blue']])
print(data)
# define one hot encoding
encoder = OneHotEncoder(drop='first', sparse=False)
# transform data
onehot = encoder.fit_transform(data)
print(onehot)
Kết quả thực hiện:
[['red']
['green']
['blue']]
[[0. 1.]
[1. 0.]
[0. 0.]]
Phần này chúng ta sẽ thực hành trên tập dữ liệu Breast Cancer đã được đề cập đến ở bài số 9. Đây là bộ dữ liệu cho bài toán Binary Classification, có 289 mẫu, mỗi mẫu có 9 features.
Đọc và kiểm tra dữ liệu:
# load and summarize the dataset
from pandas import read_csv
# load the dataset
dataset = read_csv('breast-cancer.csv', header=None)
# retrieve the array of data
data = dataset.values
# separate into input and output columns
X = data[:, :-1].astype(str)
y = data[:, -1].astype(str)
# summarize
print('Input', X.shape)
print('Output', y.shape)
Kết quả thực hiện:
Input (286, 9)
Output (286,)
Áp dụng kỹ thuật Ordinal Encoding trên tập Breast Cancer:
# ordinal encode the breast cancer dataset
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OrdinalEncoder
# load the dataset
dataset = read_csv('breast-cancer.csv', header=None)
# retrieve the array of data
data = dataset.values
# separate into input and output columns
X = data[:, :-1].astype(str)
y = data[:, -1].astype(str)
# ordinal encode input variables
ordinal_encoder = OrdinalEncoder()
X = ordinal_encoder.fit_transform(X)
# ordinal encode target variable
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(y)
# summarize the transformed data
print('Input', X.shape)
print(X[:5, :])
print('Output', y.shape)
print(y[:5])
Kết quả thực hiện:
Input (286, 9)
[[2. 2. 2. 0. 1. 2. 1. 2. 0.]
[3. 0. 2. 0. 0. 0. 1. 0. 0.]
[3. 0. 6. 0. 0. 1. 0. 1. 0.]
[2. 2. 6. 0. 1. 2. 1. 1. 1.]
[2. 2. 5. 4. 1. 1. 0. 4. 0.]]
Output (286,)
[1 0 1 0 1]
Ta thấy tất cả các giá trị categorical đều đã được chuyển sang numerical. Số lượng mẫu và features không đổi.
Thử mô hình hóa thuật toán LogisticRegression trên tập dữ liệu Breast Cancer đã được encode:
# evaluate logistic regression on the breast cancer dataset with an ordinal encoding
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OrdinalEncoder
from sklearn.metrics import accuracy_score
# load the dataset
dataset = read_csv('breast-cancer.csv', header=None)
# retrieve the array of data
data = dataset.values
# separate into input and output columns
X = data[:, :-1].astype(str)
y = data[:, -1].astype(str)
# split the dataset into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# ordinal encode input variables
ordinal_encoder = OrdinalEncoder()
ordinal_encoder.fit(X_train)
X_train = ordinal_encoder.transform(X_train)
X_test = ordinal_encoder.transform(X_test)
# ordinal encode target variable
label_encoder = LabelEncoder()
label_encoder.fit(y_train)
y_train = label_encoder.transform(y_train)
y_test = label_encoder.transform(y_test)
# define the model
model = LogisticRegression()
# fit on the training set
model.fit(X_train, y_train)
# predict on test set
yhat = model.predict(X_test)
# evaluate predictions
accuracy = accuracy_score(y_test, yhat)
print('Accuracy: %.2f' % (accuracy*100))
Kết quả thực hiện:
Accuracy: 75.79
Áp dụng kỹ thuật One Hot Encoding trên tập dữ liệu Breast Cancer.
# one-hot encode the breast cancer dataset
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# load the dataset
dataset = read_csv('breast-cancer.csv', header=None)
# retrieve the array of data
data = dataset.values
# separate into input and output columns
X = data[:, :-1].astype(str)
y = data[:, -1].astype(str)
# one hot encode input variables
onehot_encoder = OneHotEncoder(sparse=False)
X = onehot_encoder.fit_transform(X)
# ordinal encode target variable
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(y)
# summarize the transformed data
print('Input', X.shape)
print(X[:5, :])
Kết quả thực hiện:
Input (286, 43)
[[0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0. 1. 0.]
[0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0.
0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 1. 0. 0. 0. 0. 1. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0.
0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 1. 0. 0. 0. 0. 0. 1.]
[0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
1. 0. 0. 0. 1. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 1. 0. 1. 0.]]
Ta thấy rằng số lượng mẫu không đổi, nhưng số lượng features tăng lên đáng kể, từ 9 lên thành 43. Và tất cả các giá trị bây giờ đều là kiểu nhị phân.
Thử đánh giá thuật toán LogisticRegression trên tập dữ liệu Breast Cancer đã được encode này:
# evaluate logistic regression on the breast cancer dataset with a one-hot encoding
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import accuracy_score
# load the dataset
dataset = read_csv('breast-cancer.csv', header=None)
# retrieve the array of data
data = dataset.values
# separate into input and output columns
X = data[:, :-1].astype(str)
y = data[:, -1].astype(str)
# split the dataset into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# one-hot encode input variables
onehot_encoder = OneHotEncoder()
onehot_encoder.fit(X_train)
X_train = onehot_encoder.transform(X_train)
X_test = onehot_encoder.transform(X_test)
# ordinal encode target variable
label_encoder = LabelEncoder()
label_encoder.fit(y_train)
y_train = label_encoder.transform(y_train)
y_test = label_encoder.transform(y_test)
# define the model
model = LogisticRegression()
# fit on the training set
model.fit(X_train, y_train)
# predict on test set
yhat = model.predict(X_test)
# evaluate predictions
accuracy = accuracy_score(y_test, yhat)
print('Accuracy: %.2f' % (accuracy*100))
Kết quả thực hiện:
Accuracy: 70.53
Hai phương án:
Chúng ta vẫn thực hiện các kỹ thuật Data Transforms một cách bình thường. Mặc dù dữ liệu sau khi encode có thể rất lớn nhưng nói chung các thuật toán ML đều có thể xử lý được.
Như mọi lần, không có gì là tốt nhất. Hãy thử-sai trên tập dữ liệu mà chúng ta có.
Bài thứ 2 về chủ đề Data Transforms, mình đã giới thiệu 3 kỹ thuật thực hiện Data Transforms cho dữ liệu dạng categorical, đó là Ordinal Encoding, One Hot Encoding và Dummy Variable Encoding. Toàn bộ code của bài này, các bạn có thể tham khảo tại đây.
Bài tiếp theo chúng ta sẽ tìm hiểu cách thức thực hiện Data Transforms để làm cho phân phối của các features gần với phân phối Gaussian hơn. Mời các bạn đón đọc.
[1] Jason Brownlee, “Data Preparation for Machine Learning”, Book: https://machinelearningmastery.com/data-preparation-for-machine-learning/.