Ứng dụng Machine Learning vào chơi game Flappy Bird

1. Giới thiệu

Flappy Bird là một trò chơi đơn giản nhưng đầy thách thức, yêu cầu người chơi điều khiển một chú chim vượt qua các chướng ngại vật. Tuy nhiên, việc điều khiển này có thể được tự động hóa và tối ưu hóa nhờ ứng dụng Machine Learning. Bằng cách sử dụng các thuật toán học máy, chúng ta có thể huấn luyện một bot để chơi trò chơi này mà không cần can thiệp của con người. Bot sẽ học cách xử lý các tình huống trong game thông qua dữ liệu, tự động điều chỉnh chiến thuật nhằm đạt điểm số cao hơn. Việc áp dụng Machine Learning không chỉ giúp hiểu sâu hơn về quá trình học hỏi của các hệ thống tự động mà còn mở ra những tiềm năng phát triển trong ngành game và trí tuệ nhân tạo.

Mục tiêu là bot tự chơi và đạt được điểm số cao.

aicandy.vn

2. Cấu trúc project và môi trường huấn luyện

2.1. Cấu trúc dự án

root@aicandy:/aicandy/projects/aicandy_flappybird_lolmygca# tree
.
├── aicandy_core
│       ├── env_FlappyBird_test_vhnldpii.py
│       ├── env_FlappyBird_train_gsbdrvvp.py
│       ├── model_xpuihdim.py
│       └── utils_dylhxpln.py
├── aicandy_data
│       ├── background-black.png
│       ├── base.png
│       ├── pipe-green.png
│       ├── redbird-downflap.png
│       ├── redbird-midflap.png
│       └── redbird-upflap.png
├── aicandy_flappyBird_test_romddkvc.py
├── aicandy_flappyBird_train_otnlcisl.py
├── requirements.txt
└── aicandy_models
    └── flappy_bird

3 directories, 13 files
root@aicandy:/aicandy/projects/aicandy_flappybird_lolmygca#

Trong đó:

  • Thư mục aicandy_core chứa các file mã nguồn tạo môi trường và model.
  • Thư mục aicandy_data chứa dữ liệu để tạo giao diện đồ họa cho game.
  • Thư mục aicandy_models chứa các model đã được huấn luyện.
  • Tệp aicandy_flappyBird_test_romddkvc.py chứa mã nguồn chương trình để thử nghiệm.
  • Tệp aicandy_flappyBird_train_otnlcisl.py chứa mã nguồn chương trình để huấn luyện.
  • Tệp requirements.txt chứa tên các thư viện cần cài đặt để thực hiện huấn luyện.

2.2. Cài đặt môi trường

Sử dụng pygame – một thư viện phổ biến trong Python được thiết kế để phát triển trò chơi 2D và các ứng dụng đa phương tiện.

Cài đặt một số thư viện thông qua pip:

pip install -r .\requirements.txt

3. Xây dựng môi trường game Flappy bird

3.1. Giới thiệu

Môi trường game Flappy Bird được xây dựng dựa trên các thành phần chính như màn hình hiển thị, các đối tượng di chuyển và tương tác, cũng như các quy tắc vật lý. Game sử dụng thư viện pygame để tạo giao diện đồ họa 2D với các thành phần như nền, các ống cản trở, và chú chim.

Kích thước màn hình:

Trò chơi được đặt trong một khung hiển thị có kích thước 288×512, tạo nên không gian giới hạn cho các vật thể di chuyển.

Chuyển động của người chơi (Chim):

Chim di chuyển dọc theo trục y, chịu tác động của trọng lực và có thể nhảy lên khi người chơi “flap” (chạm vào màn hình). Tốc độ dọc của chim được điều chỉnh bằng trọng lực và lực đẩy khi flap.

Chướng ngại vật (Ống):

Các cột ống di chuyển ngang với tốc độ cố định từ phải sang trái. Khoảng cách giữa các cột ống được tạo ngẫu nhiên, tạo ra thách thức cho người chơi hoặc AI khi cần vượt qua.

Điểm số:

Người chơi hoặc AI kiếm điểm mỗi khi vượt qua một cột ống. Điểm số được hiển thị trên màn hình.

Môi trường này tương tác theo từng khung hình, và mỗi lần cập nhật, các vật thể trong game như chim, ống và mặt đất đều thay đổi vị trí dựa trên các yếu tố vật lý như vận tốc, trọng lực, và sự điều khiển của người chơi. Môi trường game Flappy Bird được thiết kế để có thể dễ dàng tương thích với các mô hình Machine Learning, tạo điều kiện cho AI có thể học cách điều khiển chú chim vượt qua các chướng ngại vật bằng cách quan sát trạng thái và hành động.

Về cơ bản, môi trường khi train và khi thử nghiệm tương đối giống nhau, chỉ khác ở chỗ, để tăng hiệu năng khi train thì không cần cập nhật điểm số hiện tại hoặc không cần lưu ảnh, nếu “chim” bị va chạm và kết thúc game thì chương trình tự động reset và tự chơi lại, còn với môi trường khi thử nghiệm, chúng ta có thể chèn thêm nhạc, lưu ảnh, thêm hiển thị điểm số ….

3.2. Xây dựng môi trường game khi huấn luyện

Bước 1: Tạo các hình ảnh chú chim đang bay, đang rơi, cột ống và hình nền

Sử dụng các ảnh đơn như chú chim đang bay lên, chú chim đang rơi xuống, các cột ống và ảnh nền sau đó ghép với nhau để tạo giao diện game flappybird

aicandy.vn

 

display.set_caption('AIcandy.vn Flappy Bird')
ground_img = load('aicandy_data/base.png').convert_alpha()
bg_img = load('aicandy_data/background-black.png').convert()

obstacle_imgs = [rotate(load('aicandy_data/pipe-green.png').convert_alpha(), 180),
                   load('aicandy_data/pipe-green.png').convert_alpha()]
player_imgs = [load('aicandy_data/redbird-upflap.png').convert_alpha(),
                   load('aicandy_data/redbird-midflap.png').convert_alpha(),
                   load('aicandy_data/redbird-downflap.png').convert_alpha()]

Bước 2: Tạo cột ống chuyển động

Tạo phương thức create_obstacle để sinh ra một cặp ống cản trở mới với vị trí và khoảng cách ngẫu nhiên giữa chúng. Đây là một phần quan trọng trong gameplay của Flappy Bird, nơi người chơi phải điều khiển chú chim để tránh va chạm với các ống.

def create_obstacle(self):
        x = self.display_width + 10
        gap_y = randint(2, 10) * 10 + int(self.ground_y / 5)
        return {"x_top": x, "y_top": gap_y - self.obstacle_height, "x_bottom": x, "y_bottom": gap_y + self.obstacle_gap}

Trong đó:

x = self.display_width + 10

Đoạn mã này xác định vị trí x của ống mới. Nó được đặt bên ngoài màn hình (vượt ra ngoài chiều rộng của màn hình một chút) để ống có thể di chuyển vào màn hình từ bên phải.

gap_y = randint(2, 10) * 10 + int(self.ground_y / 5)

Dòng này tạo ra một giá trị y ngẫu nhiên cho khoảng cách giữa hai ống, nhằm tạo ra độ khó khác nhau cho người chơi. Sử dụng self.ground_y / 5 để điều chỉnh giá trị ngẫu nhiên dựa trên vị trí của mặt đất, đảm bảo rằng khoảng cách giữa ống không quá thấp, tránh tình trạng tạo ra khoảng trống quá nhỏ cho người chơi.

Bước 3: Kiểm tra va chạm giữa người chơi “chim” và các chướng ngại vật.

if self.player_height + self.player_y + 1 >= self.ground_y:
     return True
overlap_rect = player_rect.clip(obstacle_rects[i])
x1 = overlap_rect.x - player_rect.x
y1 = overlap_rect.y - player_rect.y
x2 = overlap_rect.x - obstacle_rects[i].x
y2 = overlap_rect.y - obstacle_rects[i].y
if np.any(self.player_mask[self.player_frame][x1:x1 + overlap_rect.width,
     y1:y1 + overlap_rect.height] * self.obstacle_mask[i][x2:x2 + overlap_rect.width,
                                                              y2:y2 + overlap_rect.height]):
     return True

Trong đó:

if self.player_height + self.player_y + 1 >= self.ground_y

Dòng này kiểm tra xem chim có va chạm với mặt đất hay không. Nếu vị trí dọc của chim cộng với chiều cao chim (cộng thêm 1 để đảm bảo an toàn) lớn hơn hoặc bằng vị trí y của mặt đất (ground_y), phương thức sẽ trả về True, tức là chim đã va chạm với mặt đất.

if np.any(self.player_mask ….

Sử dụng mặt nạ (mask) để xác định xem có bất kỳ pixel nào từ hình ảnh chim và hình ảnh chướng ngại vật đang chồng lên nhau trong khu vực chồng lấp không. Nếu có pixel chồng chéo, phương thức sẽ trả về True, tức là có va chạm.

Bước 4: Update trạng thái game

Tạo phương thức update_game để cập nhật trạng thái của trò chơi sau mỗi khung hình, xử lý các hành động của người chơi, và quản lý điểm số, các chướng ngại vật và việc kiểm tra va chạm.
Bằng cách tính toán vị trí của chim và duyệt qua các chướng ngại vật để kiểm tra xem chim có vượt qua chướng ngại vật nào không. Nếu chim vượt qua, tăng điểm số và thiết lập phần thưởng thành 1.

player_center_x = self.player_x + self.player_width / 2
        for obstacle in self.obstacles:
            obstacle_center_x = obstacle["x_top"] + self.obstacle_width / 2
            if obstacle_center_x < player_center_x < obstacle_center_x + 5:
                self.points += 1

Tiếp theo là cập nhật vị trí của mặt đất để tạo hiệu ứng di chuyển. Đồng thời điều chỉnh tốc độ thẳng đứng bằng cách nếu tốc độ thẳng đứng của chim nhỏ hơn tốc độ tối đa và chim không đang nhảy, tăng tốc độ thẳng đứng theo trọng lực.

self.ground_x = -((-self.ground_x + 100) % self.ground_shift)

        if self.vert_speed < self.max_vert_speed and not self.flapped:
            self.vert_speed += self.gravity
        if self.flapped:
            self.flapped = False
        self.player_y += min(self.vert_speed, self.player_y - self.vert_speed - self.player_height)
        if self.player_y < 0:
            self.player_y = 0

3.3. Xây dựng môi trường game khi thử nghiệm

Môi trường game khi thử nghiệm cũng tương tự với môi trường game khi train, chúng ta chỉ cần bổ sung hàm display_score với mục đích để hiển thị điểm số lên màn hình.

font_size = 30
font = pygame.font.SysFont('Tahoma', font_size)
score_text = str(int(self.points))
score_surface = font.render(score_text, True, (255,0,0))
text_x = int(self.bg_img.get_width() - 5*len(score_text))/2 - 20
text_y = int(self.bg_img.get_height() - 60)
self.game_display.blit(score_surface, dest=(text_x, text_y))

4. Xây dựng model

Sử dụng thuật toán Deep Q-Network (DQN) – một thuật toán học tăng cường (reinforcement learning) tiên tiến, kết hợp mạng nơ-ron sâu với Q-learning truyền thống. DQN hoạt động bằng cách sử dụng mạng nơ-ron sâu để ước tính hàm Q, thay vì sử dụng bảng Q truyền thống. Điều này cho phép nó xử lý không gian trạng thái và hành động lớn hơn nhiều so với các phương pháp Q-learning cổ điển.

Phần khởi tạo mạng sử dụng 3 lớp tích chập. Mỗi lớp tích chập được kết hợp với một hàm kích hoạt ReLU để tạo ra tính phi tuyến. Tham số inplace=True trong ReLU giúp tiết kiệm bộ nhớ bằng cách thực hiện phép biến đổi trực tiếp trên dữ liệu đầu vào.

Cấu trúc này được sử dụng để trích xuất các đặc trưng từ hình ảnh đầu vào, giảm kích thước dữ liệu và tăng số lượng kênh (tức là số lượng đặc trưng) qua mỗi lớp.

self.conv1 = nn.Sequential(nn.Conv2d(4, 32, kernel_size=8, stride=4), nn.ReLU(inplace=True))
        self.conv2 = nn.Sequential(nn.Conv2d(32, 64, kernel_size=4, stride=2), nn.ReLU(inplace=True))
        self.conv3 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=3, stride=1), nn.ReLU(inplace=True))
Trong đó:
nn.Conv2d(4, 32, kernel_size=8, stride=4)

Lớp tích chập thứ nhất với 4 kênh đầu vào (4 frame liên tiếp của game), tạo 32 kênh đầu ra, sử dụng với kích thước kernel là 8 và bước nhảy là 4.

nn.Sequential(nn.Conv2d(32, 64, kernel_size=4, stride=2)

Lớp tích chập thứ hai với 32 kênh đầu vào (là 32 kênh đầu ra của lớp thứ nhất), tạo 64 kênh đầu ra, sử dụng với kích thước kernel là 4 và bước nhảy là 2.

nn.Sequential(nn.Conv2d(64, 64, kernel_size=3, stride=1)

Lớp tích chập thứ hai với 64 kênh đầu vào (là 64 kênh đầu ra của lớp thứ hai), tạo 64 kênh đầu ra, sử dụng với kích thước kernel là 3 và bước nhảy là 1.

Bướ tiếp theo là định nghĩa phương thức _create_weights để khởi tạo trọng số cho mạng neural. Đây là một bước quan trọng trong việc thiết lập mạng trước khi bắt đầu quá trình huấn luyện.

if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):

Chỉ áp dụng khởi tạo cho các lớp tích chập (Conv2d) và các lớp fully connected (Linear). Bởi vì Conv2d và Linear là các lớp chính có chứa tham số học được (learnable parameters) trong mạng neural.

nn.init.uniform_(m.weight, 0.01, 0.01)

Sử dụng phân phối đều (uniform distribution) để khởi tạo trọng số. Giá trị trọng số sẽ nằm trong khoảng [-0.01, 0.01].

Việc sử dụng phân phối đều với giá trị nhỏ giúp tránh vấn đề gradient exploding/vanishing trong quá trình huấn luyện ban đầu. Đặt bias về 0 là một lựa chọn phổ biến, cho phép mạng học các bias cần thiết trong quá trình huấn luyện.

Bước tiếp theo là định nghĩa phương thức forward để mô tả cách dữ liệu đi qua mạng từ đầu vào đến đầu ra.

output = self.conv1(input)
output = self.conv2(output)
output = self.conv3(output)
output = output.view(output.size(0), -1)
output = self.fc1(output)
output = self.fc2(output)

return output

Dữ liệu đi qua ba lớp tích chập (conv1, conv2, conv3) đã được định nghĩa trước đó. Mỗi lớp trích xuất và biến đổi các đặc trưng của đầu vào. Sau đó chuyển đổi tensor đa chiều thành vector 2D, tự động tính toán kích thước còn lại để làm phẳng tensor. Sau khi dữ liệu đã làm phẳng, dữ liệu tiếp tục đi qua hai lớp fully connected (fc1 và fc2). Và cuối cùng là trả về giá trị Q cho mỗi hành động.

5. Chương trình huấn luyện

Mục tiêu của chương trình này là sau quá trình huấn luyện sẽ tạo ra model để sau đó bot tự chơi flappy bird và đạt được điểm số cao.

Bước 1: Cấu hình các tham số cơ bản về batch, learning_rate, optimizer_type

Để tăng tính linh hoạt khi train và tùy chỉnh các tham số này, sử dụng parse_arguments() cùng với các tham số default.

parser = argparse.ArgumentParser(
        """AIcandy.vn Flappy Bird""")
    parser.add_argument("--img_size", type=int, default=84, help="The common width and height for all images")
    parser.add_argument("--batch_count", type=int, default=32, help="The number of images per batch")
    parser.add_argument("--optimizer_type", type=str, choices=["sgd", "adam"], default="adam")
    parser.add_argument("--learning_rate", type=float, default=1e-6)
    parser.add_argument("--discount_factor", type=float, default=0.99)
    parser.add_argument("--start_epsilon", type=float, default=0.1)
    parser.add_argument("--end_epsilon", type=float, default=1e-4)
    parser.add_argument("--total_iterations", type=int, default=2000000)
    parser.add_argument("--memory_size", type=int, default=50000,
                        help="Number of epoches between testing phases")
    parser.add_argument("--model_dir", type=str, default="models")

Bước 2: Khởi tạo mạng, lựa chọn optimizer, loss function và khởi tạo môi trường game

environment = FlappyBird()

khởi tạo một đối tượng của lớp FlappyBird, đại diện cho môi trường trò chơi Flappy Bird. Môi trường này có thể tương tác với mạng neural để huấn luyện agent (tác nhân).

frame, reward, done = environment.update_game(0)

Phương thức update_game(0) cập nhật trạng thái của trò chơi dựa trên hành động được thực hiện, ở đây 0 có thể đại diện cho hành động không bấm (không nhảy) trong trò chơi.

network = DeepQNetwork()    
    optimizer = torch.optim.Adam(network.parameters(), lr=config.learning_rate)
    loss_function = nn.MSELoss()    
    environment = FlappyBird()
    frame, reward, done = environment.update_game(0)

frame = torch.from_numpy(frame)

Chuyển đổi khung hình từ định dạng numpy array sang tensor của PyTorch để có thể dùng trong mạng neural.

current_state = torch.cat(tuple(frame for _ in range(4)))[None, :, :, :]

Tạo ra trạng thái hiện tại (current_state) bằng cách lặp lại tensor frame 4 lần, rồi nối chúng lại với nhau theo chiều sâu (stack). None thêm một chiều mới vào đầu tensor, thường là chiều batch để tensor có dạng (1, channels, height, width).

Bước 3: Tính toán để đưa ra quyết định “flap” hay “không flap”

Sử dụng cơ chế epsilon-greedy, một chiến lược phổ biến trong học tăng cường (reinforcement learning) để cân bằng giữa khám phá (exploration) và khai thác (exploitation).

Giá trị epsilon điều khiển xác suất để agent thực hiện khám phá (chọn một hành động ngẫu nhiên) thay vì khai thác (chọn hành động tốt nhất từ mô hình hiện tại). Khi iteration tăng lên (gần với config.total_iterations), giá trị epsilon giảm dần từ start_epsilon đến end_epsilon, có nghĩa là agent sẽ dần dần thực hiện ít khám phá hơn và bắt đầu khai thác nhiều hơn.

epsilon = config.end_epsilon + ((config.total_iterations - iteration) * (config.start_epsilon - config.end_epsilon) / config.total_iterations)

action = torch.argmax(prediction).item()
Hàm torch.argmax(prediction) tìm chỉ số của giá trị lớn nhất trong prediction, tức là hành động có khả năng nhận phần thưởng cao nhất dựa trên mô hình hiện tại.

Bước 4: Bước tiếp theo là xử lý trạng thái tiếp theo của môi trường sau khi đưa ra “action”

next_frame, reward, done = environment.update_game(action)

Cập nhật trạng thái của trò chơi bằng cách thực hiện hành động action trong môi trường environment. Với next_frame: Khung hình mới sau khi hành động được thực hiện (tức là trạng thái hiện tại của trò chơi sau hành động). reward là phần thưởng nhận được từ hành động đó (ví dụ: cộng điểm nếu vượt qua chướng ngại vật). done là Biến cờ cho biết trò chơi đã kết thúc chưa (True nếu trò chơi kết thúc, False nếu còn tiếp tục).

Mục tiêu là lấy 4 frame mới nhất để đưa các frame này vào trong mạng. Để thực hiện điều này, chúng ta nối (concatenate) tensor next_frame với tensor current_state để tạo ra trạng thái tiếp theo (next_state), sau đó cắt bớt trạng thái cũ (bỏ khung hình đầu tiên trong current_state), chỉ lấy các khung hình thứ 2 đến 4 trong dãy trạng thái hiện tại. Sau khi nối lại, trạng thái mới next_state sẽ chứa 4 khung hình: 3 khung hình trước đó (từ current_state) và khung hình mới nhất (next_frame).

next_state = torch.cat((current_state[0, 1:, :, :], next_frame))[None, :, :, :];

Bước 5: Chuẩn bị dữ liệu đầu vào cho việc huấn luyện

Ở bước này chúng ta xử lý các batch (lô) dữ liệu của hành động (action_batch), phần thưởng (reward_batch), và trạng thái tiếp theo (next_state_batch).
[1, 0] if action == 0 else [0, 1] for action in action_batch

Sử dụng kỹ thuật one-hot vector: Duyệt qua từng action trong action_batch và chuyển đổi mỗi action thành một vector one-hot: Hành động 0 được biểu diễn là [1, 0] (ví dụ: không nhảy), Hành động 1 được biểu diễn là [0, 1] (ví dụ: nhảy).

Sử dụng generator expression dùng để tạo ra một tuple gồm các trạng thái (state) từ next_state_batch. next_state_batch chứa các trạng thái tiếp theo sau khi agent thực hiện hành động tại mỗi bước trong batch. Sau đó dùng torch.cat để nối (concatenate) tất cả các trạng thái trong tuple lại với nhau dọc theo trục 0. Điều này tạo ra một tensor có chứa tất cả các trạng thái trong batch, hợp thành một tensor duy nhất với chiều batch là số lượng trạng thái.

next_state_batch = torch.cat(tuple(state for state in next_state_batch))

Bước 6: Tính toán giá trị Q mục tiêu

Quá trình tính toán giá trị Q mục tiêu (target Q-values) và cập nhật mạng neural theo thuật toán Q-learning trong học tăng cường (reinforcement learning). Nó sử dụng cơ chế hồi quy Q-learning để điều chỉnh các tham số của mạng sao cho giá trị Q dự đoán từ mô hình tiệm cận với giá trị mục tiêu tính toán từ phần thưởng nhận được và trạng thái tiếp theo.

target_q_values = torch.cat(
tuple(reward if done else reward + config.discount_factor * torch.max(q_value) for reward, done, q_value in
                  zip(reward_batch, done_batch, next_q_values)))

q_value = torch.sum(current_q_values * action_batch, dim=1)
optimizer.zero_grad()
loss = loss_function(q_value, target_q_values)
loss.backward()
optimizer.step()

Tính toán giá trị Q mục tiêu: Dựa trên phần thưởng hiện tại và giá trị Q dự đoán cho trạng thái tiếp theo. Nếu trò chơi đã kết thúc (done=True), giá trị Q mục tiêu chỉ là phần thưởng nhận được. Tính toán giá trị Q hiện tại: Chọn giá trị Q dự đoán tương ứng với hành động đã thực hiện bằng cách sử dụng one-hot vector. Lan truyền ngược và cập nhật mô hình: Tính toán mất mát giữa giá trị Q dự đoán và Q mục tiêu, sau đó sử dụng lan truyền ngược để cập nhật mô hình nhằm giảm sự khác biệt giữa chúng trong các vòng huấn luyện tiếp theo.

6. Chương trình thử nghiệm

Mục tiêu của chương trình này là sử dụng model đã được huấn luyện để chạy thử nghiệm.

Bước 1: Cấu hình tham số và load model

Để tăng tính linh hoạt cho chương trình, sử dụng để có thể thay đổi đường dẫn khi chạy chương trình.

parser = argparse.ArgumentParser("""AIcandy.vn Flappy Bird""")
parser.add_argument("--img_dim", type=int, default=84, help="The common width and height for all images")
parser.add_argument("--model_dir", type=str, default="aicandy_models")

Tiếp theo là tải model và đưa model vào chế độ đánh giá. Sử dụng GPU nếu có GPU hoặc dùng CPU nếu thiết bị GPU để tăng hiệu năng.

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = torch.load(f"{config.model_dir}/flappy_bird" if torch.cuda.is_available() else f"{config.model_dir}/flappy_bird", map_location=device)
model.eval()

Bước 2: Khởi tạo mạng, lựa chọn optimizer, loss function và khởi tạo môi trường game

environment = FlappyBird()

khởi tạo một đối tượng của lớp FlappyBird, đại diện cho môi trường trò chơi Flappy Bird. Môi trường này có thể tương tác với mạng neural để huấn luyện agent (tác nhân).

frame, reward, done = environment.update_game(0)

Phương thức update_game(0) cập nhật trạng thái của trò chơi dựa trên hành động được thực hiện, ở đây 0 có thể đại diện cho hành động không bấm (không nhảy) trong trò chơi.

network = DeepQNetwork()
optimizer = torch.optim.Adam(network.parameters(), lr=config.learning_rate)
loss_function = nn.MSELoss()
environment = FlappyBird()
frame, reward, done = environment.update_game(0)

frame = torch.from_numpy(frame)

Chuyển đổi khung hình từ định dạng numpy array sang tensor của PyTorch để có thể dùng trong mạng neural.

current_state = torch.cat(tuple(frame for _ in range(4)))[None, :, :, :]

Tạo ra trạng thái hiện tại (current_state) bằng cách lặp lại tensor frame 4 lần, rồi nối chúng lại với nhau theo chiều sâu (stack). None thêm một chiều mới vào đầu tensor, thường là chiều batch để tensor có dạng (1, channels, height, width).

Bước 3: Tính toán để đưa ra quyết định “flap” hay “không flap”

Trong chế độ đánh giá, các hành động “flap” hoặc “không flap” được quyết định bằng việc tìm chỉ số của giá trị lớn nhất trong prediction, tức là hành động có khả năng nhận phần thưởng cao nhất dựa trên mô hình hiện tại.

action = torch.argmax(prediction).item()

Bước 4: Xử lý trạng thái tiếp theo của môi trường sau khi đưa ra “action”

Bước này cập nhật trò chơi dựa trên hành động đã thực hiện (action), như một bước trong quá trình học tăng cường. Cần lưu ý là chúng ta chỉ cần lấy phần hình ảnh tương ứng với màn hình hiển thị của trò chơi, từ độ rộng của trò chơi (game.display_width) đến độ cao của mặt đất (game.ground_y). Điều này loại bỏ các phần không liên quan của khung hình, chẳng hạn như phần phía dưới mặt đất hoặc phần ngoài màn hình hiển thị.
state là tensor đại diện cho trạng thái hiện tại của trò chơi (bao gồm nhiều khung hình). state[0, 1:, :, :] lấy các khung hình từ vị trí 1 trở đi, tức là loại bỏ khung hình đầu tiên (cũ nhất). Việc loại bỏ khung hình cũ nhất giúp agent luôn có thông tin cập nhật về trò chơi mà không bị “quá khứ quá xa” ảnh hưởng.
Nối (concatenate) khung hình mới next_frame với chuỗi khung hình hiện tại (đã loại bỏ khung hình cũ nhất). Việc nối này tạo ra một trạng thái mới với các khung hình liên tiếp gần nhất, bao gồm khung hình vừa nhận được từ môi trường. Điều này giúp mô hình có thông tin về sự thay đổi trạng thái (các khung hình liên tiếp) để đưa ra quyết định tốt hơn trong bước tiếp theo.

next_frame, reward, done = game.update_game(action)
            next_frame = process_image(next_frame[:game.display_width, :int(game.ground_y)], config.img_dim, config.img_dim)
            next_frame = torch.from_numpy(next_frame).to(device)
            next_state = torch.cat((state[0, 1:, :, :], next_frame))[None, :, :, :]

Thực hiện liên tục bước 4 cho tới khi kết thúc trò chơi (chú chim bị va chạm vào ống).

7. Demo và source code

Xem video demo bot chơi Flappy bird tại đây

Tải miễn phí source code tại đây

 

Chúc bạn thành công trong hành trình khám phá và ứng dụng trí tuệ nhân tạo vào học tập và công việc. Đừng quên truy cập thường xuyên để cập nhật thêm kiến thức mới tại AIcandy