Professional Documents
Culture Documents
I
Chi Thanh Nguyen
April 2024
Mục lục
1 Trí tuệ nhân tạo 2
1
Nhập môn A.I Trang 2
1. Tìm kiếm (Search): Tìm lời giải cho một bài toán. Ví dụ như tìm con
đường tốt nhất để đi từ điểm xuất phát đến điểm đến hay tìm nước đi tiếp
theo trong một trò chơi.
2. Kiến thức (Knowledge): Biểu diễn thông tin và rút ra suy luận từ nó.
3. Tính không chắc chắn (Uncertainly): Xử lý các sự kiện không chắc chắn
bằng xác xuất.
4. Tối ưu hoá (Optimization): Không chỉ tìm lời giải đúng cho một bài toán
mà là một cách tốt hơn, tốt nhất để giải bài toán.
5. Học (Learning): Cải thiện hiệu suất dựa vào quyền truy cập đến dữ liệu
và trải nghiệm. Ví dụ: email của bạn có thể phân biệt giữa thư rác và thư
không phải thư rác bằng những kinh nghiệm trước đây.
6. Mạng lưới thần kinh (Neural Networks): Một cấu trúc chương trình
lấy ý tưởng từ não người, có thể xử lý những tác vụ một cách hiệu quả.
7. Ngôn ngữ (Language): Xử lý ngôn ngữ tự nhiên, hay ngôn ngữ của con
người.
Một ví dụ là câu đố 15, khi bạn phải trượt các ô để sắp xếp chúng theo đúng thứ
tự.
Một ví dụ khác là bài toán tìm đường đi qua mê cung. Coi điểm bắt đầu là A và
điểm kết thúc là B, thì bạn cần tìm ra chuỗi hành động chính xác để đi từ trạng
thái ban đầu là A đến mục tiêu là B mà phải tuân theo quy luật là không thể đi
xuyên tường.
Chuyển đến một ví dụ thực tế hơn là chỉ đường lái xe. Google Maps sử dụng một
loại thuật toán tìm kiếm để chỉ đường từ điểm bắt đầu đến điểm đích, tuỳ vào
từng ngã rẽ, tình hình giao thông hay thời gian trong ngày.
Để tiếp cận đến những bài toá tìm kiếm, đây là một số thuật ngữ cần biết (để
tránh gây ra sai sót khi dịch những thuật ngữ sau đây sẽ là tiếng anh):
• Agent: Một thực thể nhận thức môi trường và hoạt động theo môi trường
đó. Ví dụ trong vấn đề chỉ đường lái xe thì agent sẽ là đại diện của chiếc xe
cần quyết định chuỗi hành động để tới được điểm đến.
• State: Một cấu hình của một agent trong môi trường của nó. Ví dụ trong
câu đố 15, một state là một cách mà các ô được sắp xếp.
• Initial State: Là state khi mà thuật toán sắp xếp bắt đầu. Ví dụ trong bài
toán mê cung thì điểm xuất phát A chính là initial state.
• Actions: Những lựa chọn có thể thực hiện trong một state. Chúng ta có
thể định nghĩa chính xác hơn actions như một hàm. Định nghĩa một hàm
ACT ION S(S) là một hàm nhận vào một state là s, sẽ trả ra một tập hợp
tất cả các hành động có thể được thực hiện ở state s.
Tuy nhiên máy tính sẽ không thể hiểu được những hành động này vì thế
chúng ta cần phải chuyển chúng thành một dạng dữ liệu mà máy tính có thể
hiểu, từ đó chúng ta đến với thuật ngữ tiếp theo.
• State Space: Là tập hợp của các state có thể được đến từ initial state thông
qua bất kỳ chuỗi actions nào. Ví dụ đối với câu đố 15. Gọi state space của
câu đố 15 là S, ta có thể tính |S|.
Dẽ dàng tính được có 16! hoán vị của câu đố 15 nhưng từ một initial state
chúng ta chỉ có thể tới được một nửa số đó. Điều nay xảy ra là do tính chẵn
lẻ. Trong trường hợp này tính chẵn lẻ là hoán vị của các ô trong câu đó có
số lần đảo là chắn hay lẻ. Ví dụ chúng ta chỉ có thể đi từ state S1 đến state
S2 nếu như chúng có cùng tính chẵn lẻ.
16!
Vì vậy |S| = ≈ 1013
2
State space có thể được minh hoạ bằng một đồ thị có hưỡng với mỗi state
là một node và mỗi action là một cạnh.
• Goal Test: Là một cách kiểm tra xem state hiện tại có phải state mục tiêu
không.
• Path Cost: Là một chi phí bằng số gắn với đường đi được cho. Ví dụ đối
với các ứng dụng chỉ đường nó không chỉ đưa cho chúng ta đường đi mà nó
đưa chúng ta đương đi ngắn nhất, tìm cách nhanh nhất có thể để đến được
goal state. Một ví dụ là đồ thị sau đây:
Mỗi quãng đướng đi từ node này sang node khác sẽ tốn chi phí. Tuy nhiên
đối với trường hợp của câu đố 15 không ảnh hưởng gì khi chúng ta trượt
sang trái hay trượt sang phải, nên chúng ta có thể coi chi phí của mỗi action
là một hằng số (Ví dụ như 1).
Node lưu trữ các thông tin phục vụ cho việc tìm kiếm. State dùng để kiểm tra
liệu đã đến goal state chưa. Nếu như đến goal state thì path cost sẽ giúp tìm ra
optimal solution. Khi đã chon được node thì parent node và action sẽ giúp chúng
ta duyệt ngược lại để tìm ra chuỗi actions.
Tuy nhiên node chỉ là một cấu trúc dữ liệu nên không thể nào tìm kiếm mà chỉ lưu
trữ thông tin. Để thực sự tìm kiếm chúng ta sẽ lưu trữ tất cả các node trong một
cấu trúc dữ liệu duy nhất được gọi là frontier (biên giới), một cơ chế để quản lý
các node. Frontier đại diện cho các node mà chúng ta sắp khám phá và các node
mà chúng ta chưa khám phá. Sử dụng frontier, chúng ta có thể tạo ra thuật toán
tìm kiếm cơ bản như sau:
• Frontier sẽ bắt đầu với việc chứa initial state (do là node gốc nên các giá trị
ngoại trừ state được gán giá trị None)
• Sau đó chúng ta sẽ lặp lại các hành động sau cho đến khi tìm được solution:
– Nếu như frontier rỗng, không có lời giải cho bài toán.
– Xoá một node khỏi frontier.
– Nếu như node vừa xoá chứa goal state, chúng tra trả về solution
– Nếu không, chúng ta xét các node mà có để được tạo ra từ node vừa
xoá, rồi thêm chúng vào frontier.
Đầu tiên chúng ta bắt đầu với node A, frontier lúc này sẽ là [A].
Sau đó chúng ta xoá một node khỏi frontier, lúc này do chỉ có node A, nên
chúng ta xoá nó đi. Do A không phai goal state nên chúng ta tiếp tục mở rộng
đến các node con của A là B, rồi thêm B vào frontier. Lúc này frontier là [B].
Do B cũng không phải là goal state, nên chúng ta xoá B khỏi frontier và xét
các node con của B là C và D, tạm thời không quan tâm đến thứ tự chúng ta sẽ
thêm C và D vào frontier và giả sử frontier của chúng ta là: [C, D].
Tiếp theo chúng ta xoá một node khỏi frontier, bỏ qua thứ tự chúng ta chọn
node C. Do C không phải goal state nên chúng ta thêm node con của C vào fron-
tier là E. Frontier lúc này là: [D, E].
Tiếp tục xét D, do không phải goal state nên chúng ta thêm node con của D
là F vào frontier. Frotier lúc này là: [E, F ].
Xét node E, do E là goal state nên chúng ta kết thúc vòng lặp tại đây. Sau
khi duyệ ngược lại từ E ta có mảng thứ tự các node là là [E, C, B, A], do duyệt
ngược lại nên chúng ta phải đảo ngược mảng lại để có được đường đi từ A đến E
là: [A, B, C, E].
Thực hiện thuật toán frontier bắt đầu với một node A.
Chúng ta xoá A khoải frontier, do A không phải goal state nên chúng ta thêm
node con của A là B vào frontier.
Tiếp tục xoá B, do B vẫn không phải goal state nên chúng ta thêm tất cả
node mà có thể đến từ B vào frontier. Do lúc này B được nối ngược lại với A, nên
frontier lúc này sẽ là: [A, C, D].
Xoá node A khỏi frontier, nhân thầy đây tiếp tục là bước ban đầu, nên nếu
tiếp tục sẽ tạo ra vòng lặp vô tân. Vì vậy chúng ta không thể áp dụng thuật toán
trên với mọi trường hợp. ⇒ Chúng ta cần cải tiến thuật toán.
2.2.2 Thuật toán tìm kiếm theo độ sâu (Depth-First Search - DFS)
Nhận thấy vấn đề của thuật toán vừa rồi là có thể quay lại những node đã
được khám phá.
Chúng ta sẽ lưu trữ tất cả những node đã khám phá vào một tập hợp gọi là explored
set. Trong quá trình mở rộng node chúng ta chỉ thêm vào frontier các node chưa
có trong explored set.
Từ đó chúng ta có thuật toán sau:
• Frontier sẽ bắt đầu với việc chứa initial state (do là node gốc nên các giá trị
ngoại trừ state được gán giá trị None)
• Sau đó chúng ta sẽ lặp lại các hành động sau cho đến khi tìm được solution:
– Nếu như frontier rỗng, không có lời giải cho bài toán.
– Xoá một node khỏi frontier.
– Nếu như node vừa xoá chứa goal state, chúng tra trả về solution
– Thêm node vừa xoá vào explored set.
– Nếu không, chúng ta xét các node mà có để được tạo ra từ node vừa
xoá, rồi thêm chúng vào frontier nếu như chúng chưa có trong frontier
và explored set.
Tuy nhiên cách chúng ta xoá node khỏi frontier vẫn đang là chọn ngẫu nhiên,
chúng ta cần một thứ tự để xoá phần tử. Để làm như vậy chúng ta sử dụng một
cấu trúc dữ liệu là stack (ngăn xếp) - một kiểu dữ liệu hoạt động theo cấu trúc
LIFO (Last In - First Out). Stack hoạt động giống một mảng, phần từ cuối cùng
được gọi là đỉnh stack. Một stack có các thao tác như sau:
Chúng ta sẽ cho frontier hoạt động theo cấu trúc của stack. Mỗi khi thêm/xoá
node, chúng ta sẽ sử dụng các thao tác push và pop tương ứng.
Chúng ta sẽ áp dụng thuật toán mới với bài toán vừa rồi:
- Frontier = [A].
Xoá A khỏi frontier do A không phải goal state, chúng ta thêm B vào frontier.
Để đánh dấu đã khám phá A, chúng ta thêm A vào explored set:
- Frontier = [B].
Xoá A khỏi frontier do A không phải goal state, chúng ta thêm các node có
thể đến được B vào frontier là A, C, D, tuy nhiên do A đã nằm trong explored set
nên chúng ta không thêm vào. Để đánh dấu đã khám phá B, chúng ta thêm B
vào explored set:
Lúc này frontỉer có hai phần tử áp dụng theo cấu trúc của stack chúng ta sẽ
loại bỏ D, do D không phải goal state, nên chúng ta sẽ thêm F vào frontier đồng
thời thêm D vào explored set:
- Frontier = [C, F ].
Tiếp tục xoá node ở đỉnh stack, chúng ta xoá F khỏi frontier. Do không có
node nào đến được F nên chúng ta thêm F vào explored set:
- Frontier = [C].
Chúng ta xoá C khỏi frontier. Do không phải goal state nên chúng ta thêm E
vào frontier và thêm C vào explored set:
- Frontier = [E].
Xoá E khỏi frontier, do E là goal state nên chúng ta trả về solution về kết thúc
vòng lặp.
Chúng ta gọi phiên bản này của thuật đoán là thuật toán tìm kiếm theo chiều sâu
(Depth-First Search), viết tắt là DFS. Một thuật toán luôn luôn mở rộng node sâu
nhất ở frontier.
2.2.3 Thuật toán tìm kiếm theo chiều rộng (Breadth-First Search -
BFS)
DFS không phải lựa chọn duy nhất để tìm kiếm chúng ta có một thuật toán
khác tương tự là BFS. Khác với DFS luôn luôn mở rộng node sâu nhất ở frontier,
BFS luôn luôn mở rộng node nông nhất ở frontier.
Để sử dụng thuật toán này chúng ta đơn giản chỉ cần sửa đổi một chút ở DFS,
thay vì sử dụng stack để biểu diễn frontier chúng ta sử dụng một cấu trúc dữ liệu
khác là queue (hàng đợi).
Queue là một cấu trúc dữ liệu hoạt động theo cấu trúc FIFO (First In - First
Out), các thao tác cơ bản trên queue là:
- Frontier = [A].
- Frontier = [B].
Đây là lúc mà thuật toán này trở nên khác biệt so với DFS. Sử dụng thao tác
pop của queue, chúng ta xoá C khỏi frontier, sau đó thêm E vào cuối frontier :
Tiếp tục áp dụng thao tác trên, chúng ta xoá D khỏi frontier, sau đố thêm F
vào:
- Frontier = [E, F ].
Xoá E khỏi frontier, nhận thầy E là goal state, chúng ta trả về solution và
dừng vòng lặp.
Ở đây chúng ta sẽ khám phá theo từng hướng, giả sử khám phá hướng bên trái
trước. Bước đầu chúng ta đi đến ngã ba.
Tiếp tục làm giống như trước chúng ta khám phá từng bên:
Nhận thấy khi đi về đo không thể đến đích chúng ta chuyển hướng sang bên
phải.
Nhận thầy đối với cách này chúng ta chó thể tìm được optimal solution, tuy
nhiên chúng ta thử xét ví dụ sau.
Cách bên phải là optimal solution, tuy nhiện nêu may mắn chúng ta có thể có
được optimal solution còn nếu không thì chúng ta chỉ có thể có được solution.
Qua đó chúng ta có được ưu/nhược điểm của DFS:
• Ưu điểm
– Trong trường hợp tốt nhất DFS là nhanh nhất. Nếu như đủ may mắn
thì thuật toán này liên tục chon đường đi ngắn nhất và đến đích.
• Nhược điểm
Do xét mọi con đường nên BFS có thể cho ra optimal solution.
Ban đầu khi chưa có ngã rẽ BFS hoạt động giống DFS.
Đây là lúc xảy ra sự khác biệt khi BFS xet mọi con đường thay vì tường con
đường như DFS.
• Ưu điểm
• Nhược điểm
– Thuật toán nay hầu như sẽ mất nhiều thời gian hơn thời gian chạy tối
thiểu.
• Cho người dùng chọn DFS hoặc BFS để tìm đường trong mê cung
class Maze:
def __init__(self, filename):
with open(filename) as f:
contents = f.read()
if contents.count('A') != 1:
raise Exception(\'The maze must contains exactly one start point')
if contents.count('B') != 1:
contents = contents.strip().splitlines()
self.row = len(contents)
self.col = max([len(i) for i in contents])
self.wall = []
self.solution = None
self.explored_set = None
for i in range(self.row):
t = []
for j in range(self.col):
try:
if contents[i][j] == ' ': t.append(True)
elif contents[i][j] == 'A':
self.start = (i, j)
t.append(True)
elif contents[i][j] == 'B':
self.goal = (i, j)
t.append(True)
else: t.append(False)
except IndexError:
t.append(True)
self.wall.append(t)