16/10/2018, 21:14

Dự đoán kết quả game PUBG với Machine Learning? Chuyện thật cứ như đùa.

Đối với các anh em làm về Machine Learning (ML) và Deep Learning (DL) thì Kaggle là một địa chỉ khá quen thuộc. Trên Kaggle , có rất nhiều challenge với các độ khó và dễ khác nhau, rất thích hợp cho các bạn làm về ML, DL luyện tập thêm. Hôm vừa rồi mình thấy có 1 challenge khá hay về việc dữ đoán ...

Đối với các anh em làm về Machine Learning (ML) và Deep Learning (DL) thì Kaggle là một địa chỉ khá quen thuộc. Trên Kaggle, có rất nhiều challenge với các độ khó và dễ khác nhau, rất thích hợp cho các bạn làm về ML, DL luyện tập thêm. Hôm vừa rồi mình thấy có 1 challenge khá hay về việc dữ đoán thứ hạng kết thúc trong game PUBG dựa trên các thông số cho trước. Mình thấy đây là 1 challenge khá hay và trong bài viết này mình sẽ thử xây dựng 1 mạng nơ-ron đơn giản để quẩy challenge này xem sao.

Để có thể tiến hành các phần tiền xử lý dữ liệu cũng như huấn luyện các mô hình Machine Learning khác nhau, ta cần tạo một môi trường ảo (trong bài viết này mình sẽ sử dụng virtualenv với Python 3 trên nền Ubuntu 16.04). Đầu tiên là phần cài đặt pip3 và môi trường ảo sử dụng virtualenv:

sudo apt install python3-pip
pip3 install virtualenv
mkdir -p ~/virtualenv
virtualenv -p python3 ~/virtualenv/pubg_env

Tiếp theo là cài đặt các package cần thiết (numpy, pandas, tensorflow, keras) trong môi trưởng ảo vừa rồi:

source ~/virtualenv/pubg_env/bin/activate
pip install numpy pandas tensorflow keras

Ok, phần cài đặt ban đầu đã xong, bắt tay vào quẩy thôi nào.

Trước tiên, các bạn cần download dữ liệu trên trang Kaggle (chỉ cần click vào nút Download All là có thể tải được tất cả dữ liệu về trong 1 nốt nhạc). Sau khi giải nén ra, ta thấy có 3 file .csv: train.csv, test.csv, và sample_submission.csv. File sample_submission.csv là file submission mẫu còn file test.csv chứa data của tập public test. Trong phần tiền xử lý dữ liệu này, ta chỉ cần quan tâm tới file train.csv do file này chứa những dữ liệu phục vụ trực tiếp trong quá trình huấn luyện các thuật toán Machine Learning.

              Id  groupId  matchId  assists  boosts  damageDealt  DBNOs      ...       swimDistance  teamKills  vehicleDestroys  walkDistance  weaponsAcquired  winPoints  winPlacePerc
0              0       24        0        0       5       247.30      2      ...             0.0000          0                0       782.400                4       1458        0.8571
1              1   440875        1        1       0        37.65      1      ...             0.0000          0                0       119.600                3       1511        0.0400
2              2   878242        2        0       1        93.73      1      ...             0.0000          0                0      3248.000                5       1583        0.7407
3              3  1319841        3        0       0        95.88      0      ...             0.0000          0                0        21.490                1       1489        0.1146
4              4  1757883        4        0       1         0.00      0      ...             0.0000          0                0       640.800                4       1475        0.5217
5              5  2200824        5        0       2       128.10      0      ...             0.0000          0                0      1016.000                4       1500        0.9368
6              6  2568717        6        1       0       130.30      0      ...             0.0000          0                0       280.100                3       1495        0.3721
...

Ngắm nghía qua 1 chút thì ta thấy dữ liệu trong file train.csv bao gồm rất nhiều hàng (mỗi hàng ứng với 1 sample), và 26 cột ứng với 26 features của 1 sample. Thông tin cụ thể về ý nghĩa của mỗi feature các bạn có thể tham khảo thêm tại trang chủ Kaggle. Ở đây mình xin giới thiệu một vài tham số tiêu biểu:

  1. assists: tổng số mạng bạn knock-out đối thủ (bạn knock-out và đồng đội của bạn hạ gục mạng đó)
  2. boosts: tổng số vật phẩm tăng lực (nước, painkiller, adrenaline) được sử dụng
  3. kills: tổng số mạng hạ gục trong 1 trận
  4. damageDealt: tổng số sát thương gây ra
  5. ...
  6. winPlacePerc: Đây chính là output cần dự đoán, có giá trị từ 0 tới 1. 0 có nghĩa là vị trí top cuối (last place in the macth) và 1 có nghĩa là vị trí top đầu (first place in the match).

Với data như trên, ta có thể dễ dàng nhận thấy dữ liệu thô có khá nhiều thuộc tính thuộc các khoảng giá trị khác nhau. Ví dụ: trường rideDistance là khoảng cách di chuyển trên xe, đơn vị là mét và khoảng giá trị từ 0 tới 48390; trong khi đó trường boosts là tổng số vật phẩm tăng lực được sử dụng lại có khoảng giá trị chỉ từ 0 tới 18. Chính vì vậy, nếu ta không có bước tiền xử lý mà đưa luôn dữ liệu thô vào training thì kết quả sẽ không tốt.

Có 2 cách để chuẩn hóa dữ liệu cơ bản rất hay được sử dụng:

  • Min-max scaling: công thức như sau:

x=x−minmax−minx=frac {x-min} {max - min} x=maxminxmin

trong đó min và max lần lượt là giá trị nhỏ nhất và lớn nhất. Với cách này, dữ liệu sẽ được chuẩn hóa về khoảng từ 0 tới 1.

  • Standarization: công thức như sau:

x=x−meanstandard deviationx= frac {x - mean}{standard deviation} x=standard deviationxmean

trong đó mean là giá trị trung bình và standard deviation là độ lệch chuẩn. Trong bài viết này mình sẽ sử dụng cách này để chuẩn hóa dữ liệu.

Vấn đề thứ 2 là không phải thuộc tính cũng có liên quan tới kết quả đầu ra cuối cùng, do đó mình cần loại bỏ các thuộc tính không cần thiết. Cụ thể trong bài này, ta có thể bỏ đi thông tin của 3 trường: Id, groupId, matchId. Viết 1 hàm để đọc file csv và xử lý dữ liệu:

# import các thư viện cần thiết
import pandas as pd
import numpy as np
def read_csv(file_name='train.csv'):
    # list_mean và list_std là 2 lists để lưu giá trị mean và standard deviation cho từng trường.
    list_mean = []
    list_std = []
    # đọc file csv dùng thư viện pandas
    meta_data = pd.read_csv(file_name)
    # Drop 3 thuộc tính không cần thiết như đã nêu ở trên và trường winPlacePerc là output
    data = meta_data.drop(columns=['Id', 'groupId', 'matchId', 'winPlacePerc'], axis=1)
    # Lấy ground-truth output
    label = meta_data['winPlacePerc']
    # Lấy tên của các trường dữ liệu
    attributes = data.columns.values
    for att in attributes
        # tính mean và standard deviation cho tường trường tương ứng và append kết quả vào list
        mean = data[att].mean()
        std = data[att].std()
        # Normalize dữ liệu theoi công thức
        data[att] = (data[att] - mean) / std
        list_mean.append(mean)
        list_std.append(std)
    mean_and_std = np.array([list_mean, list_std])
    # Convert từ data frame của pandas sang numpy array
    data_array = data.values
    label_array = label.values
    # Lưu data đã qua xử lý dưới dạng file numpy array (.npy)
    np.save('data.npy', data_array)
    np.save('label.npy', label_array)
    np.save('mean_std.npy', mean_and_std)

Sau khi chạy function trên, ta sẽ thu được dữ liệu đã qua xử lý trong file data.npy và label tương ứng trong file label.npy. File mean_std.npy lưu giá trị mean và standard deviation của từng trường, sở dĩ ta cần làm việc này vì đến lúc dự đoán output cho sample mới, ta cầm chuẩn hóa dữ liệu theo đúng công thức standardization như lúc tiền xử lý dữ liệu trước khi training.

Sau khi hoàn thành bước tiền xử lý dữ liệu, ta đã thu được dữ liệu đã qua chuẩn hóa. Việc tiếp theo là cài đặt một mô hình nơ-ron và huấn luyện nó với tập dữ liệu vừa xử lý xong. Ở đây mình chỉ xây dựng một mô hình mạng nơ-ron cơ bản với các lớp fully-connected xếp liên tiếp nhau.

# import các thư viện cần thiết
from keras.layers import *
from keras.models import *
def create_model(input_feature=22, output_feature=1, activation='elu', dropout=0.1, no_hiddens=5, no_neurons=50):
    input_layer = Input(shape=(input_feature,), name='input_layer')
    temp_layer = input_layer
    for i in range(no_hiddens):
        temp_layer = Dense(units=no_neurons, activation=activation)(temp_layer)
        temp_layer = Dropout(dropout)(temp_layer)
        temp_layer = BatchNormalization()(temp_layer)
    output_layer = Dense(units=output_feature, activation='sigmoid', name='output_layer')(temp_layer)
    model = Model(input_layer, output_layer)
    print (model.summary())
    return model

Ở đây, input_feature=22 do số trường gốc của dữ liệu là 26, trừ 1 trường là label và 3 trường vừa bị loại bỏ đi, ta chỉ còn lại 22 thuộc tính. Các tham số khác để định nghĩa topology của mạng như: activation, dropout, hay số layer ẩn no_hiddens các bạn hoàn tòan có thể thay đổi tùy tý lúc gọi hàm. Ở lớp đầu ra của mạng, ta chỉ 1 nơ-ron do output của ta chỉ là 1 số thực có khoảng từ 0-1. Mình dùng hàm sigmoid do khoảng giá trị của output chỉ từ 0 tới 1.

0<sigmoid(x)=exex+1<10 < sigmoid(x) = frac{e^x}{e^x + 1} < 1 0<sigmoid(x)=ex+1ex<1

Còn về hàm loss, do ở đây là bài toán regression (không phải classification) nên mình sử dụng hàm trung bình bình phương (mean squared error). Với đặc thù của bài toán tao có thể kết hợp hàm sigmoid và mean squared error lại, mặc dù việc này nghe có vẻ khá điên rồ. Sau khi đã có mô hình, việc tiếp theo là compile model và định nghĩa các tham số trong quá trình training:

# import các thư viện cần thiết
from keras.callbacks import ModelCheckpoint
def train(model, data, label, optimizer, save_path, epochs, batch_size, validation_split):
    '
    model: mô hình mạng nơ-ron cần huấn luyện
    data, label: dữ liệu là nhãn tương ứng để training
    optimizer: thuật toán đựa trên phương pháp gradient descent để tối ưu hàm loss
    save_path: đường dẫn để lưu mô hình trong quá trình training
    validation_split: % data training được tách ra để validate
    '
    model.compile(optimizer=optimizer, loss='mean_squared_error')
    checkpoint = ModelCheckpoint(filepath=save_path, verbose=1, save_best_only=True)
    model.fit(x=data, y=label, batch_size=batch_size, epochs=epochs, callbacks=[checkpoint,], validation_split=validation_split)

Sau khi đã tiền xử lý dữ liệu và xây dựng mô hình mạng nơ-ron xong, ta có thể bắt đầu quá trình huấn luyện được rồi.

# import các thư viện cần thiết
from keras.optimizers import Adam
# Config các tham số cần thiết:
save_path = 'model/best_model.h5'
if not os.path.exists('model'):
    os.mkdir('model')
epochs = 5
batch_size = 128
# 0.002 -> 2% data training sẽ được tách ra làm validate
validation_split = 0.002
# load lại data và label đã qua xử lý từ file .npy
data = np.load('data.npy')
label = np.load('label.npy')
# Tạo 1 model với hàm create_model() được định nghĩa ở bên trên.
# Để cho đơn giản nên các tham số mình sẽ để mặc định
model = create_model()
# Bắt đầu training thôi nào
train(model=model, data=data, label=label, optimizer=optimizer, save_path=save_path, epochs=epochs, batch_size=batch_size, validation_split=validation_split)

Kết quả là:

Epoch 1/5
2018-10-15 19:50:59.681936: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
4348621/4348621 [==============================] - 117s 27us/step - loss: 0.0148 - val_loss: 0.0071

Epoch 00001: val_loss improved from inf to 0.00710, saving model to model/best_model.h5
Epoch 2/5
4348621/4348621 [==============================] - 117s 27us/step - loss: 0.0107 - val_loss: 0.0070

Epoch 00002: val_loss improved from 0.00710 to 0.0070, saving model to model/best_model.h5
Epoch 3/5
4348621/4348621 [==============================] - 117s 27us/step - loss: 0.0105 - val_loss: 0.0060

Epoch 00003: val_loss improved from 0.0070 to 0.0060, saving model to model/best_model.h5
Epoch 4/5
4348621/4348621 [==============================] - 117s 27us/step - loss: 0.0104 - val_loss: 0.0064

Epoch 00004: val_loss did not improve from 0.0060
Epoch 5/5
4348621/4348621 [==============================] - 112s 26us/step - loss: 0.0103 - val_loss: 0.0062

Epoch 00005: val_loss did not improve from 0.0060

Như vậy là quá trình training đã kết thúc, ta đã có 1 mô hình đã được huấn luyện và có thẻ sử dụng nó để dự đoán cho dữ liệu trong file test.csv. Ở đây mình chỉ dừng lại ở 5 epochs, các bạn có thể training mô hình lâu hơn và có thể thay đổi các tham số khác của mạng nơ-ron để có thể có kết quả tốt hơn. Một điểm nữa cần lưu ý là trước khi feed dữ liệu để predict, các bạn cần thực hiện bước chuẩn hóa dữ liệu với mean và standard deviation trước, nếu không kết quả sẽ bị sai lệch đi rất nhiều.

Trong bài viết này mình đã chia sẻ cùng các bạn cách tạo huấn luyện một mô hình mạng nơ-ron cơ bản để dự đoán kết quả về thứ hạng của người chơi trong trò chơi PUBG nổi tiếng. Model mình vừa mới xây dựng còn rất đơn giản, các bạn có thể thử thêm một vài kiến trúc mạng khác nữa để có thể cải tiến độ chính xác. Ngoài ra, Kaggle là một nơi khá hay để các bạn có đam mê về Machine Learning có thể thử sức. Rất hi vọng bài viết này của mình giúp được các bạn có một cái nhìn thực tế về việc triển khai một bài toán Machine Learning trong thực tế: từ việc tìm và tải dữ liệu, tiền xử lý dữ liệu, xây dựng mô hình Machine Learning, cho tới bước training mô hình. Cảm ơn các bạn đã theo dõi bài viết của mình, hẹn gặp lại các bạn trong các bài viết sau.

0