๐ ์ธ๊ณต์ง๋ฅ ํ์ ์๊ณ ๋ฆฌ์ฆ - ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ
๐ 1. ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ์ด๋?
โ ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ์ด๋?
๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ(Game Search Algorithm)์ AI๊ฐ ๊ฒ์ ๋ด์์ ์ต์ ์ ์ ๋ต์ ์ฐพ๊ธฐ ์ํด ๊ฐ๋ฅํ ๋ชจ๋ ์๋ฅผ ํ์ํ๊ณ ํ๊ฐํ๋ ๋ฐฉ์์
๋๋ค.
์ด ์๊ณ ๋ฆฌ์ฆ๋ค์ ์๋๋ฐฉ์ ํ๋์ ๊ณ ๋ คํ์ฌ ์ต๊ณ ์ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
๐ ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ์ ํน์ง
โ ์ ์ ์์ง์์ ์์ธกํ์ฌ ์ต์ ์ ์๋ฅผ ์ฐพ์
โ ์น๋ฆฌ ํ๋ฅ ์ ๋์ด๊ธฐ ์ํด ๋ค์ํ ๊ฒฝ์ฐ์ ์๋ฅผ ํ๊ฐ
โ ์์ ํ ํ์์ด ์ด๋ ค์ด ๊ฒฝ์ฐ, ํด๋ฆฌ์คํฑ ํจ์๋ฅผ ํ์ฉํ์ฌ ํ์์ ์ต์ ํ
๐ ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ์ด ์ฌ์ฉ๋๋ ๋ถ์ผ
์ฒด์ค, ๋ฐ๋, ์ฅ๊ธฐ โ ์ ๋ต์ ์ธ ํด ๊ธฐ๋ฐ ๊ฒ์
์คํํฌ๋ํํธ, ๋กค(LoL), Dota 2 โ ์ค์๊ฐ ์ ๋ต ๊ฒ์(RTS)
ํฑํํ (Tic-Tac-Toe), ์ค๋ชฉ โ ๊ฐ๋จํ ๊ฒ์ AI
๐ 2. ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ(Mini-Max Algorithm)
โ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์ด๋?
๋ฏธ๋๋งฅ์ค(Mini-Max) ์๊ณ ๋ฆฌ์ฆ์ ์ต์ ์ ์ ๋ต์ ์ ํํ๊ธฐ ์ํด ๋ ํ๋ ์ด์ด์ ์ต์ ์ ์๋ฅผ ๊ฐ์ ํ๊ณ ํ์ํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
์ฆ, ๋ด๊ฐ ์ต์ ์ ์๋ฅผ ์ ํํ๊ณ , ์๋๋ฐฉ์ ๋์๊ฒ ์ต์
์ ์๋ฅผ ์ ํํ๋ค๊ณ ๊ฐ์ ํ์ฌ ๊ฒ์์ ํ์ํฉ๋๋ค.
๐ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์ ํต์ฌ ์๋ฆฌ
Max ํ๋ ์ด์ด (AI) โ ๊ฐ์ฅ ๋์ ์ ์๋ฅผ ์ป๋ ์๋ฅผ ์ ํ
Min ํ๋ ์ด์ด (์๋๋ฐฉ) โ AI๊ฐ ๊ฐ์ฅ ๋ฎ์ ์ ์๋ฅผ ์ป๋๋ก ๋ฐฉํดํ๋ ์๋ฅผ ์ ํ
๊ฒ์ ํธ๋ฆฌ๋ฅผ ํ์ํ์ฌ ์ต์ ์ ์๋ฅผ ๊ฒฐ์
๐ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ ๋์ ๊ณผ์
ํ์ฌ ์ํ์์ ๊ฐ๋ฅํ ๋ชจ๋ ์๋ฅผ ํธ๋ฆฌ ํํ๋ก ํ์ฅ
์ตํ๋จ(๋ฆฌํ) ๋ ธ๋์์ ์นํจ๋ฅผ ํ๊ฐํ์ฌ ์ ์๋ฅผ ๋ถ์ฌ
Min ๋ ๋ฒจ์์๋ ์ต์๊ฐ์ ์ ํ, Max ๋ ๋ฒจ์์๋ ์ต๋๊ฐ์ ์ ํ
๋ฃจํธ ๋ ธ๋๊น์ง ๋ฐ๋ณตํ์ฌ ์ต์ ์ ์ ํ ๊ฒฐ์
โ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ ์ฝ๋ ๊ตฌํ (Python)
def minimax(depth, node_index, maximizing_player, scores, height):
if depth == height:
return scores[node_index]
if maximizing_player:
return max(minimax(depth + 1, node_index * 2, False, scores, height),
minimax(depth + 1, node_index * 2 + 1, False, scores, height))
else:
return min(minimax(depth + 1, node_index * 2, True, scores, height),
minimax(depth + 1, node_index * 2 + 1, True, scores, height))
scores = [3, 5, 2, 9, 12, 5, 23, 23]
height = 3
print("์ต์ ์ ๊ฐ:", minimax(0, 0, True, scores, height))
๐ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ ์คํ ๊ฒฐ๊ณผ ์์
์ต์ ์ ๊ฐ: 12
๐ก ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์ ์ต์ ์ ์ ๋ต์ ๋ณด์ฅํ์ง๋ง, ๊ฒ์ ํธ๋ฆฌ๊ฐ ํฌ๋ฉด ์ฐ์ฐ๋์ด ๊ธ๊ฒฉํ ์ฆ๊ฐํ ์ ์์!
๐ 3. ์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ(ฮฑ-ฮฒ Pruning)
โ ฮฑ-ฮฒ ๊ฐ์ง์น๊ธฐ๋?
์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ(Alpha-Beta Pruning)๋ ๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์์ ๋ถํ์ํ ํ์์ ์ค์ฌ ์๋๋ฅผ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์
๋๋ค.
์ฆ, ์นํจ์ ์ํฅ์ ์ฃผ์ง ์๋ ๊ฐ์ง๋ฅผ ์๋ผ๋ด์ด ์ฐ์ฐ๋์ ์ค์
๋๋ค.
๐ ์ํ(ฮฑ)์ ๋ฒ ํ(ฮฒ)์ ๊ฐ๋
์ํ(ฮฑ) โ Max ํ๋ ์ด์ด(๋ด๊ฐ)์๊ฒ ๊ฐ์ฅ ์ข์ ๊ฐ (์ต๋๊ฐ)
๋ฒ ํ(ฮฒ) โ Min ํ๋ ์ด์ด(์๋๋ฐฉ)์๊ฒ ๊ฐ์ฅ ์ข์ ๊ฐ (์ต์๊ฐ)
ฮฑ โฅ ฮฒ๊ฐ ๋๋ ๊ฒฝ์ฐ ํ์์ ์ค๋จ (๊ฐ์ง์น๊ธฐ ๋ฐ์)
๐ ์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ ๋์ ๊ณผ์
๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก ํธ๋ฆฌ๋ฅผ ํ์
Max ๋ ธ๋์์ ํ์ฌ ์ต์์ ์ ํ(ฮฑ)์ ์ ์ฅํ๊ณ , ๋ ์ข์ ์ ํ์ด ๋ํ๋๋ฉด ์ ๋ฐ์ดํธ
Min ๋ ธ๋์์ ํ์ฌ ์ต์์ ์ ํ(ฮฒ)์ ์ ์ฅํ๊ณ , ๋ ๋ฎ์ ๊ฐ์ด ๋ํ๋๋ฉด ์ ๋ฐ์ดํธ
๋ง์ฝ ฮฑ โฅ ฮฒ๊ฐ ๋๋ฉด ํ์์ ์ค๋จ(๊ฐ์ง์น๊ธฐ ์ํ)
โ ์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ ์ฝ๋ ๊ตฌํ (Python)
def alpha_beta(depth, node_index, maximizing_player, scores, alpha, beta, height):
if depth == height:
return scores[node_index]
if maximizing_player:
max_value = float('-inf')
for i in range(2):
value = alpha_beta(depth + 1, node_index * 2 + i, False, scores, alpha, beta, height)
max_value = max(max_value, value)
alpha = max(alpha, max_value)
if beta <= alpha:
break # ๊ฐ์ง์น๊ธฐ ๋ฐ์
return max_value
else:
min_value = float('inf')
for i in range(2):
value = alpha_beta(depth + 1, node_index * 2 + i, True, scores, alpha, beta, height)
min_value = min(min_value, value)
beta = min(beta, min_value)
if beta <= alpha:
break # ๊ฐ์ง์น๊ธฐ ๋ฐ์
return min_value
# ์์ ๋ฐ์ดํฐ
scores = [3, 5, 2, 9, 12, 5, 23, 23]
height = 3
print("์ต์ ์ ๊ฐ:", alpha_beta(0, 0, True, scores, float('-inf'), float('inf'), height))
๐ ์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ ์คํ ๊ฒฐ๊ณผ ์์
์ต์ ์ ๊ฐ: 12
๐ก ๋ฏธ๋๋งฅ์ค๋ณด๋ค ํจ์ฌ ๋น ๋ฅด๊ฒ ์คํ๋๋ฉฐ, ์ต์ ์ ์ ํ์ ๋ณด์ฅํจ!
๐ 4. ๋ชฌํ ์นด๋ฅผ๋ก ํธ๋ฆฌ ํ์(Monte Carlo Tree Search, MCTS)
โ MCTS๋?
MCTS(๋ชฌํ
์นด๋ฅผ๋ก ํธ๋ฆฌ ํ์)๋ ๋ฌด์์ ์๋ฎฌ๋ ์ด์
์ ์คํํ์ฌ ์น๋ฅ ์ด ๋์ ์ ๋ต์ ์ ํํ๋ ๋ฐฉ์์
๋๋ค.
์ฆ, ๊ฐ๋ฅํ ๋ชจ๋ ์๋ฅผ ํ์ํ์ง ์๊ณ , ์ ๋งํ ์๋ฅผ ์๋ฎฌ๋ ์ด์
ํ์ฌ ์ต์ ์ ์ ๋ต์ ๊ฒฐ์ ํฉ๋๋ค.
ํนํ ๋ฐ๋, ์ฒด์ค, ํฌ์ปค ๋ฑ ํ๋ฅ ๊ณผ ์ ๋ต์ด ์ค์ํ ๊ฒ์์์ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
๐ MCTS์ 4๋จ๊ณ ๊ณผ์
1โฃ Selection (์ ํ)
ํ์ฌ ์ํ์์ ๊ฐ์ฅ ์ ๋งํ ๋ ธ๋(์)๋ฅผ ์ ํ
ํ์์ ์งํํ ๋ ธ๋๋ฅผ ๊ฒฐ์ (UCB1 ๊ณต์ ํ์ฉ)
2โฃ Expansion (ํ์ฅ)
์ ํ๋ ๋ ธ๋์์ ์๋ก์ด ๊ฐ๋ฅํ ์๋ฅผ ์์ฑํ์ฌ ํ์ฅ
3โฃ Simulation (์๋ฎฌ๋ ์ด์ )
๋ฌด์์ ํ๋ ์ด์์(Random Rollout)์ ์ํ
ํด๋น ์๊ฐ ์น๋ฆฌ๋ก ์ด์ด์ง๋์ง ํ๊ฐ
4โฃ Backpropagation (์ญ์ ํ)
์นํจ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ชจ ๋ ธ๋๋ก ์ ํํ์ฌ ๊ฐ ์์ ์น๋ฅ ์ ๋ฐ์ดํธ
๐ก ์ด ๊ณผ์ ์ ๋ฐ๋ณตํ์ฌ ๊ฐ์ฅ ์น๋ฅ ์ด ๋์ ์๋ฅผ ์ ํ!
โ MCTS์ ํน์ง
โ ์๋ฒฝํ ํ์์ด ์ด๋ ค์ด ๊ฒ์์์ ์ ์ฉ
โ ์ค์๊ฐ ํ์ต ๊ฐ๋ฅ (๊ฐํํ์ต๊ณผ ๊ฒฐํฉ ๊ฐ๋ฅ)
โ ๋ฐ๋ AI (์ํ๊ณ , AlphaGo), ์ฒด์ค AI ๋ฑ์ ํ์ฉ
โ ํ์ํ ๊ฐ๋ฅ์ฑ์ด ๋์ ๋ถ๋ถ์ ์ง์คํ์ฌ ์ฐ์ฐ๋ ์ ์ฝ
โ MCTS ์ฝ๋ ๊ตฌํ (Python, ๊ฐ๋จํ ๊ตฌ์กฐ)
import math
import random
class Node:
def __init__(self, state, parent=None):
self.state = state
self.parent = parent
self.children = []
self.visits = 0
self.value = 0
def is_fully_expanded(self):
return len(self.children) > 0
def best_child(self, exploration_weight=1.0):
return max(self.children, key=lambda c: c.value / (c.visits + 1e-6) + exploration_weight * math.sqrt(math.log(self.visits + 1) / (c.visits + 1e-6)))
def mcts_search(root, itermax=1000):
for _ in range(itermax):
node = root
while node.is_fully_expanded():
node = node.best_child()
result = random.choice([-1, 1]) # ๋ฌด์์ ์นํจ ์๋ฎฌ๋ ์ด์
while node is not None:
node.visits += 1
node.value += result
node = node.parent
return root.best_child(exploration_weight=0)
# ์์ ์คํ
root = Node("Root State")
best_move = mcts_search(root, 1000)
print("MCTS ์ ํ๋ ์ต์ ๊ฒฝ๋ก:", best_move.state)
๐ MCTS ์คํ ๊ฒฐ๊ณผ ์์
MCTS ์ ํ๋ ์ต์ ๊ฒฝ๋ก: Root State (์๋ฎฌ๋ ์ด์
๊ธฐ๋ฐ ์ต์ ์ ์ ํ)
๐ฏ ๋ง๋ฌด๋ฆฌ: ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต
์๊ณ ๋ฆฌ์ฆํน์ง์ฅ์ ๋จ์ | |||
๋ฏธ๋๋งฅ์ค | ์์ ํ ๊ฒ์ ํธ๋ฆฌ ํ์ | ์ต์ ํด ๋ณด์ฅ | ์ฐ์ฐ๋ ๋ง์ |
ฮฑ-ฮฒ Pruning | ๋ฏธ๋๋งฅ์ค ์ต์ ํ | ๋ถํ์ํ ์ฐ์ฐ ์ค์ | ์ฌ์ ํ ๊ณ์ฐ๋ ๋ง์ |
MCTS | ํ๋ฅ ์ ์๋ฎฌ๋ ์ด์ | ๋น ๋ฅธ ์ ๋ต ์ ํ ๊ฐ๋ฅ | ์ผ์ ์์ค์ ๋ฌด์์์ฑ ํฌํจ |
๐ ๊ฒ์ ํ์ ์๊ณ ๋ฆฌ์ฆ์ ๊ฒ์์ ํน์ฑ์ ๋ง๊ฒ ์ ํํด์ผ ํฉ๋๋ค!
๋ฏธ๋๋งฅ์ค ์๊ณ ๋ฆฌ์ฆ์ ์ฒด์ค, ํฑํํ ์ ๊ฐ์ ์ ํํ๋ ํด ๊ธฐ๋ฐ ๊ฒ์์์ ์ฌ์ฉ๋จ
์ํ-๋ฒ ํ ๊ฐ์ง์น๊ธฐ(ฮฑ-ฮฒ Pruning)๋ ๋ฏธ๋๋งฅ์ค๋ฅผ ์ต์ ํํ์ฌ ๊ณ์ฐ๋์ ์ค์ด๋ ๋ฐ ํจ๊ณผ์
๋ชฌํ ์นด๋ฅผ๋ก ํธ๋ฆฌ ํ์(MCTS)์ ๋ฐ๋, ํฌ์ปค, ์ค์๊ฐ ์ ๋ต ๊ฒ์(RTS)์์ ํ๋ฅ ๊ธฐ๋ฐ์ผ๋ก ํจ๊ณผ์ ์ธ ํ์ ์ํ