XGBoost – Bài 4: Chuẩn bị dữ liệu cho XGBoost model

XGBoost là một thuật toán thuộc họ Gradient Boosting. Những ưu điểm vượt trội của nó đã được chứng minh qua các cuộc thi trên kaggle. Dữ liệu đầu vào cho XGBoost model phải ở dạng số. Nếu dữ liệu không ở dạng số thì phải được chuyển qua dạng số (numeric) trước khi đưa vào XGBoost model để train. Có một vài phương pháp để thực hiện việc này, hãy cùng nhau điểm qua trong phần còn lại của bài viết.

Sau khi xem hết bài viết này, bạn sẽ biết:

  • Làm thế nào để mã hóa chuỗi (string) đầu ra cho việc phân loại?
  • Làm thế nào để mã hóa dữ liệu đầu vào kiểu "categorical" sử dụng mã hóa one-hot (one hot encoding).
  • Làm thế nào để tự động xử lý vấn đề thiếu dữ liệu trong dataset (missing data)?

1. Mã hóa chuỗi đầu ra

Chúng ta sẽ sử dụng bài toán phân loại hoa Iris để minh họa cho vấn đề này. Đưa vào các số liệu đo đặc các thành phần của hoa Iris, cần dự đoán hoa Iris đó thuộc loại nào (đầu ra dự đoán của model là các chuỗi ký tự).

Hãy xem ví dụ của dataset:

5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa

XGBoost sẽ không thể mô hình hóa bài toán này bởi vì nó yêu cầu dữ liệu đầu vào phải ở dạng số. Chúng ta có thể dễ dang chuyển đổi từ kiểủ chuổi (string) sang kiểu số (integer) sử dụng lớp LabelEncoder của thư viện sklearn. Ba giá trị đầu ra kiểu string (Iris-setosa, Iris-versicolor, Iris-virginica) sẽ được ánh xạ thành các giá trị số tương ứng (0, 1, 2):

# encode string class values as integers
label_encoder = LabelEncoder()
label_encoder = label_encoder.fit(Y)
label_encoded_y = label_encoder.transform(Y)

Chúng ta cần lưu lại bộ mã hóa này để chuyển đổi ngược lại từ số sang chuỗi trong quá trình dự đoán.

Dưới đây là toàn bộ code minh họa việc việc đọc dataset, mã hóa dữ liệu đầu ra, train XGBoost model và đánh giá độ chính xác của model đó:

# multiclass classification
from pandas import read_csv
from XGBoost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
# load data
data = read_csv('iris.csv', header=None)
dataset = data.values
# split data into X and y
X = dataset[:,0:4]
Y = dataset[:,4]
# encode string class values as integers
label_encoder = LabelEncoder()
label_encoder = label_encoder.fit(Y)
label_encoded_y = label_encoder.transform(Y)
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(X, label_encoded_y,test_size=test_size, random_state=seed)
# fit model on training data
model = XGBClassifier()
model.fit(X_train, y_train)
print(model)
# make predictions for test data
predictions = model.predict(X_test)
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

Chạy đoạn code trên, được kết quả như sau:

XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1,
              objective='multi:softprob', random_state=0, reg_alpha=0,
              reg_lambda=1, scale_pos_weight=None, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)
Accuracy: 92.00%

2. Mã hóa one-code dữ liệu kiểu Categorical

Rất nhiều bộ dataset chứa các dữ liệu kiểu categorical. Trong phần này, chúng ta sử dụng bộ dataset breast cancer để làm việc. Bộ dataset này miêu tả thông tin của y tế của các bệnh nhân, nhãn của nó chỉ ra bệnh nhân đó có bị ung thư hay không.

Ví dụ của bộ dữ liệu này như bên dưới:

'40-49','premeno','15-19','0-2','yes','3','right','left_up','no','recurrence-events'
'50-59','ge40','15-19','0-2','no','1','right','central','no','no-recurrence-events'
'50-59','ge40','35-39','0-2','no','2','left','left_low','no','recurrence-events'
'40-49','premeno','35-39','0-2','yes','3','right','left_low','yes','no-recurrence-events'
'40-49','premeno','30-34','3-5','yes','2','left','right_up','no','recurrence-events'

Chúng ta nhìn thấy rằng tất cả 9 giá trị input đều ở là kiểu categorical và được thể hiện ở dạng string. Đây cũng là bài toán phân lớp nhị phân và nhãn cần dự đoán cũng đang ở dạng string. Vì vậy, ta có thể sử dụng lại cách tiếp cận ở phần trước, chuyển các giá trị dạng string sang integer sử dụng LabelEncoder.

# encode string input values as integers
features = []
for i in range(0, X.shape[1]):
    label_encoder = LabelEncoder()
    feature = label_encoder.fit_transform(X[:,i])
    features.append(feature)
encoded_x = numpy.array(features)
encoded_x = encoded_x.reshape(X.shape[0], X.shape[1])

Khi sử dụng LabelEncoder, XGBoost có thể hiểu rằng các giá trị encoded integer của mỗi input feature có mối quan hệ thứ tự. Ví dụ, đối với input feature breast-quad, giá trị left-up được mã hóa là 0, left-low được mã hóa là 1. Điều này thực tế là không đúng trong trường hợp này. Để tránh điều này, ta phải ánh xạ các giá trị integer thành 1 giá trị kiểu binary.
Ví dụ, các giá trị của biến đầu vào breast-quad là:

left-up
left-low
right-up
right-low
central

được ánh xạ thành 5 giá trị binary tương ứng:

1,0,0,0,0
0,1,0,0,0
0,0,1,0,0
0,0,0,1,0
0,0,0,0,1

Điều này được gọi là OneHotEncoder. Ta có thể mã hóa OneHot tất cả các input features kiểu categorical sử dụng lớp OneHotEncoder của thư viện scikit-leaen:

onehot_encoder = OneHotEncoder(sparse=False, categories=✬auto✬)
feature = onehot_encoder.fit_transform(feature)

OneHot Encoder cho tất cả input features của dữ liệu:

# encode string input values as integers
columns = []
for i in range(0, X.shape[1]):
    label_encoder = LabelEncoder()
    feature = label_encoder.fit_transform(X[:,i])
    feature = feature.reshape(X.shape[0], 1)
    onehot_encoder = OneHotEncoder(sparse=False, categories=✬auto✬)
    feature = onehot_encoder.fit_transform(feature)
    columns.append(feature)
# collapse columns into array
encoded_x = numpy.column_stack(columns)

Nếu biết chắc chắn răng một input feature nào đó có mối quan hệ về thứ tự, có thể bỏ qua OneHot Encoder mà chỉ cần LabelEncoder cho feature đó.

Toàn bộ source code của phần này:

# binary classification, breast cancer dataset, label and one hot encoded
from numpy import column_stack
from pandas import read_csv
from XGBoost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# load data
data = read_csv(✬datasets-uci-breast-cancer.csv✬, header=None)
dataset = data.values
# split data into X and y
X = dataset[:,0:9]
X = X.astype(str)
Y = dataset[:,9]
# encode string input values as integers
columns = []
for i in range(0, X.shape[1]):
    label_encoder = LabelEncoder()
    feature = label_encoder.fit_transform(X[:,i])
    feature = feature.reshape(X.shape[0], 1)
    onehot_encoder = OneHotEncoder(sparse=False, categories=✬auto✬)
    feature = onehot_encoder.fit_transform(feature)
columns.append(feature)
# collapse columns into array
encoded_x = column_stack(columns)
print("X shape: : ", encoded_x.shape)
# encode string class values as integers
label_encoder = LabelEncoder()
label_encoder = label_encoder.fit(Y)
label_encoded_y = label_encoder.transform(Y)
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(encoded_x, label_encoded_y,
    test_size=test_size, random_state=seed)
# fit model on training data
model = XGBClassifier()
model.fit(X_train, y_train)
print(model)
# make predictions for test data
predictions = model.predict(X_test)
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

Chạy code trên ta được kết quả:

('X shape: : ', (286, 43))
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)
Accuracy: 68.42%

3. Giải quyết vấn đề missing data

XGBoost có thể tự động học cách để đưa ra cách giải quyết tốt nhất cho vấn đề missing data. Trên thực tế, XGBoost được thiết kế để làm việc với sparse data, giống như one hot encoded data ở phần trước. Chi tiết hơn về các kỹ thuật xử lý missing data của XGBoost, có thể tham khảo Section 3.4 Sparsity-aware Split Finding trong bài báo XGBoost: A Scalable Tree Boosting System.

Trong phần này, ta sẽ sử dụng Horse Colic dataset để minh họa khả năng xử lý missing data của XGBoost. Trong dataset này, tỷ lệ mising data tương dối lớn, rơi vào khoảng 30%.

Dễ dàng đọc được dataset này với thư viện Pandas:

dataframe = read_csv("horse-colic.csv", delim_whitespace=True, header=None)
dataframe.head()

Ta có thể quan sát thấy rằng, missing data được đánh dấu bằng dấu ?.

 0   1        2      3    4   5  6  7  8  9  10 11 12 13 14    15 16 17     18    19 20    21 22  23     24  25  26  27
0  2   1   530101  38.50   66  28  3  3  ?  2  5  4  4  ?  ?     ?  3  5  45.00  8.40  ?     ?  2   2  11300   0   0   2
1  1   1   534817   39.2   88  20  ?  ?  4  1  3  4  2  ?  ?     ?  4  2     50    85  2     2  3   2   2208   0   0   2
2  2   1   530334  38.30   40  24  1  1  3  1  3  3  1  ?  ?     ?  1  1  33.00  6.70  ?     ?  1   2      0   0   0   1
3  1   9  5290409  39.10  164  84  4  1  6  2  2  4  4  1  2  5.00  3  ?  48.00  7.20  3  5.30  2   1   2208   0   0   1
4  2   1   530255  37.30  104  35  ?  ?  6  2  ?  ?  ?  ?  ?     ?  ?  ?  74.00  7.40  ?     ?  2   2   4300   0   0   2

Thay ? bởi giá trị 0:

# set missing values to 0
X[X == '?'] = 0
# convert to numeric
X = X.astype('float32')

Bài toán đối với dataset này là Binary Classification, nhãn bao gồm 2 giá trị là 1 và 2. Ta dễ dàng chuyển sang 0 và 1 sử dụng LabelEncoder:

# encode Y class values as integers
label_encoder = LabelEncoder()
label_encoder = label_encoder.fit(Y)
label_encoded_y = label_encoder.transform(Y)

Code đầy đủ:

# binary classification, missing data
from pandas import read_csv
from XGBoost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
# load data
dataframe = read_csv("horse-colic.csv", delim_whitespace=True, header=None)
dataset = dataframe.values
# split data into X and y
X = dataset[:,0:27]
Y = dataset[:,27]
# set missing values to 0
X[X == '?'] = 0
# convert to numeric
X = X.astype('float32')
# encode Y class values as integers
label_encoder = LabelEncoder()
label_encoder = label_encoder.fit(Y)
label_encoded_y = label_encoder.transform(Y)
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(X, label_encoded_y,
    test_size=test_size, random_state=seed)
# fit model on training data
model = XGBClassifier()
model.fit(X_train, y_train)
print(model)
# make predictions for test data
predictions = model.predict(X_test)
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

Output:

XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)
Accuracy: 82.83%

Hãy kiểm tra khả năng của XGBoost bằng cách thử thay missing value với các giá trị khác nhau:

  • Thay missing value bởi 1

    X[X == '?`] = 1

    Kết quả:

    Accuracy: 81.82%
  • Thay missing value bởi NaN

    X[X == '?`] = 1

    Kết quả:

    Accuracy: 83.84%
  • Thay thế missing value bằng giá trị trung bình (mean) của toàn bộ feature đó

    # impute missing values as the mean
    imputer = SimpleImputer()
    imputed_x = imputer.fit_transform(X)

    Source đầy đủ:

    # binary classification, missing data, impute with mean
    import numpy
    from pandas import read_csv
    from XGBoost import XGBClassifier
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score
    from sklearn.preprocessing import LabelEncoder
    from sklearn.preprocessing import Imputer
    # load data
    dataframe = read_csv("horse-colic.csv", delim_whitespace=True, header=None)
    dataset = dataframe.values
    # split data into X and y
    X = dataset[:,0:27]
    Y = dataset[:,27]
    # set missing values to NaN
    X[X == '?'] = numpy.nan
    # convert to numeric
    X = X.astype(✬float32✬)
    # impute missing values as the mean.
    imputer = Imputer()
    imputed_x = imputer.fit_transform(X)
    # encode Y class values as integers
    label_encoder = LabelEncoder()
    label_encoder = label_encoder.fit(Y)
    label_encoded_y = label_encoder.transform(Y)
    # split data into train and test sets
    seed = 7
    test_size = 0.33
    X_train, X_test, y_train, y_test = train_test_split(imputed_x, label_encoded_y,
    test_size=test_size, random_state=seed)
    # fit model on training data
    model = XGBClassifier()
    model.fit(X_train, y_train)
    print(model)
    # make predictions for test data
    predictions = model.predict(X_test)
    # evaluate predictions
    accuracy = accuracy_score(y_test, predictions)
    print("Accuracy: %.2f%%" % (accuracy * 100.0))

    Kết quả:

    XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)
    Accuracy: 81.82%

Có thể thấý rằng, đối với bài toán này, giải pháp thay thế missing value bằng NaN mang lại kết quả tốt nhất. Trong các bài toán thực tế, chúng ta cũng cần phải thử-sai nhiều cách khác nhau để chọn được phương pháp tối ưu cho bài toán đó.

4. Kết luận

Trong bài viết này chúng ta đã cùng nhau tìm hiểu các cách để chuẩn bị dữ liệu cho việc train XGBoost model. Cụ thể:

  • Mã hóa dữ liệu kiểu string bằng LabelEncoder
  • Mã hóa dữ liệu kiểu categorical bằng OneHot Encoder
  • Xử lý missing data

Trong bài tiếp theo, chúng ta sẽ tìm hiểu các phương pháp đánh giá hiệu năng của XGBoost model.

Toàn bộ source code của bài này các bạn có thể tham khảo trên github cá nhân của mình tại github.

Xem bài viết gốc tại đây.

Leave a Reply