AI下棋为什么越来越强,认知增强学习课程从入门到实战教你掌握强化学习核心技术
一、从AlphaGo到现在的AI棋手,人类见证了一场”智力革命”
你有没有想过,为什么几十年前连象棋都下不过人类初学者的AI,现在却能在围棋上轻松碾压世界冠军?答案其实藏在强化学习这个概念里。
强化学习(Reinforcement Learning,简称RL)是机器学习的一个分支,它模仿的是人类和动物通过”试错”来学习决策的过程。和传统的监督学习不同,监督学习是给AI一堆带标签的数据让它分类,比如”这张图是猫,那张图是狗”。而强化学习更像是你小时候学骑自行车——没人告诉你每一步该怎么做,你只能摔倒了爬起来,慢慢地就找到了平衡。
AI下棋的过程其实就是一个标准的强化学习场景:
- 智能体(Agent):就是AI棋手本身
- 环境(Environment):棋盘和游戏规则
- 状态(State):当前棋盘上棋子的分布
- 动作(Action):AI选择落子的位置
- 奖励(Reward):赢了得正分,输了得负分
- 策略(Policy):AI根据当前状态决定下一步的完整方案
二、强化学习的核心三要素:值函数、策略和模型
要理解AI为什么越来越强,得先搞明白强化学习里的三个核心组件。
2.1 策略(Policy)——AI的”行动指南”
策略是强化学习的灵魂,它告诉AI在某个状态下应该做什么动作。策略可以是确定性的,比如”看到状态A就执行动作B”;也可以是随机的,比如”在状态A下有60%概率执行动作B,40%概率执行动作C”。
在围棋AI里,策略网络(Policy Network)直接输出每个合法落子点的概率分布。AlphaGo早期的版本用的是蒙特卡洛树搜索(MCTS)配合策略网络,后来AlphaZero直接用一个深度神经网络同时输出策略和价值估计。
# 一个简单的策略网络示例
import torch
import torch.nn as nn
class ChessPolicyNet(nn.Module):
def __init__(self, board_size=19, num_actions=362):
super().__init__()
# 输入:当前棋盘状态(362个通道的19x19网格)
self.conv1 = nn.Conv2d(362, 128, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(128)
self.conv2 = nn.Conv2d(128, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
# 策略头:输出每个动作的概率
self.policy_head = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * 19 * 19, 1024),
nn.ReLU(),
nn.Linear(1024, num_actions)
)
# 价值头:评估当前局面优劣
self.value_head = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * 19 * 19, 256),
nn.ReLU(),
nn.Linear(256, 1),
nn.Tanh() # 输出-1到1之间的值
)
def forward(self, state):
x = torch.relu(self.bn1(self.conv1(state)))
x = torch.relu(self.bn2(self.conv2(x)))
policy = self.policy_head(x)
value = self.value_head(x)
return policy, value
2.2 值函数(Value Function)——AI的”评估系统”
值函数帮助AI判断某个状态好不好。在围棋里,值函数会告诉你”这个局面白棋赢了80%“还是”黑棋占了优势”。
值函数有两种常见形式:
- 状态价值函数 V(s):从状态s开始,预期能获得的总奖励
- 动作价值函数 Q(s,a):在状态s执行动作a后,预期能获得的总奖励
DeepMind的AlphaGo Zero直接用神经网络逼近Q值,这个网络被称为”价值网络”。
# Q-learning中的值函数更新
class QLearningAgent:
def __init__(self, learning_rate=0.1, discount_factor=0.99, epsilon=0.1):
self.q_table = {} # 存储状态-动作的值
self.lr = learning_rate # 学习率
self.gamma = discount_factor # 折扣因子
self.epsilon = epsilon # 探索概率
def get_q_value(self, state, action):
if state not in self.q_table:
self.q_table[state] = {}
return self.q_table[state].get(action, 0.0)
def update_q_value(self, state, action, reward, next_state, done):
current_q = self.get_q_value(state, action)
if done:
target = reward
else:
next_max_q = max(self.q_table.get(next_state, {}).values(), default=0)
target = reward + self.gamma * next_max_q
# Q值更新公式
new_q = current_q + self.lr * (target - current_q)
if state not in self.q_table:
self.q_table[state] = {}
self.q_table[state][action] = new_q
2.3 模型(Model)——AI的”世界模拟器”
不是所有强化学习算法都需要模型。基于模型的算法(Model-Based RL)试图学习环境的动态规律,比如”如果我在这步走这个位置,对手最可能怎么回应”。
而像AlphaGo Zero、AlphaZero这样的算法采用”无模型”(Model-Free)方法,它们不尝试预测环境变化,而是直接从大量对弈经验中学习。
三、从蒙特卡洛到深度强化学习:AI下棋能力的进化史
3.1 第一代:蒙特卡洛树搜索(MCTS)
2016年AlphaGo击败李世石时,它用的是MCTS结合深度神经网络的方法。MCTS本质上是一种树搜索算法,它通过模拟大量随机对弈来评估每个可能动作的价值。
MCTS的四个步骤:
- 选择(Selection):从根节点开始,沿着树向下选择最有潜力的节点
- 扩展(Expansion):如果节点未完全扩展,添加一个新子节点
- 模拟(Simulation):从新节点开始,用随机策略进行模拟对弈
- 回溯(Backpropagation):将模拟结果回溯更新路径上所有节点的值
class MonteCarloTreeSearch:
def __init__(self, c_puct=1.0):
self.c_puct = c_puct # 探索常数
self.node_counts = {}
self.node_values = {}
def ucb_score(self, node, parent_total_visits):
"""Upper Confidence Bound for Trees (UCT) 公式"""
if self.node_counts.get(node, 0) == 0:
return float('inf') # 优先探索未访问的节点
exploitation = self.node_values[node] / self.node_counts[node]
exploration = self.c_puct * self._compute_exploration_bonus(
parent_total_visits, self.node_counts[node]
)
return exploitation + exploration
def _compute_exploration_bonus(self, parent_visits, child_visits):
return (
math.sqrt(math.log(parent_visits)) / (1 + child_visits)
)
def search(self, state, num_iterations):
"""执行MCTS搜索"""
for _ in range(num_iterations):
node = self._select(state)
value = self._simulate(state, node)
self._backpropagate(node, value)
# 返回最被访问次数最多的动作(而非价值最高的)
best_action = max(
self.children[state],
key=lambda a: self.node_counts.get((state, a), 0)
)
return best_action
3.2 第二代:深度Q网络(DQN)
2013年DeepMind发表的DQN论文震惊了业界。他们用深度神经网络来近似Q值函数,成功让AI学会了玩 Atari 游戏。
DQN的两个关键技术突破:
- 经验回放(Experience Replay):将智能体过去的经验存储在回放缓冲区中,随机采样训练,打破数据间的时间相关性
- 目标网络(Target Network):使用一个独立的”目标网络”来计算目标Q值,保持训练稳定性
class DQNAgent:
def __init__(self, state_dim, action_dim, lr=1e-3):
self.q_network = nn.Sequential(
nn.Linear(state_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, action_dim)
)
self.target_network = nn.Sequential(
nn.Linear(state_dim, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, action_dim)
)
# 同步目标网络参数
self.target_network.load_state_dict(self.q_network.state_dict())
self.optimizer = torch.optim.Adam(self.q_network.parameters(), lr=lr)
self.memory = ReplayBuffer(capacity=10000)
def select_action(self, state, epsilon=0.1):
"""ε-贪心策略:以ε概率随机探索,否则利用当前最佳策略"""
if random.random() < epsilon:
return random.randint(0, self.action_dim - 1)
with torch.no_grad():
state = torch.FloatTensor(state).unsqueeze(0)
q_values = self.q_network(state)
return q_values.argmax(dim=1).item()
def learn(self, batch_size=64, gamma=0.99):
if len(self.memory) < batch_size:
return
# 采样一批经验
states, actions, rewards, next_states, dones = self.memory.sample(batch_size)
# 计算当前Q值
current_q = self.q_network(states).gather(1, actions)
# 计算目标Q值(使用目标网络)
with torch.no_grad():
next_q = self.target_network(next_states).max(dim=1)[0]
target_q = rewards + gamma * next_q * (1 - dones)
# 计算损失并更新
loss = nn.MSELoss()(current_q, target_q.unsqueeze(1))
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 定期更新目标网络
self.update_target_network()
3.3 第三代:策略梯度方法(Policy Gradient)
DQN有个局限性:它只能处理离散动作空间。对于围棋这种361个可能落子点的动作空间,DQN的计算量会爆炸。策略梯度方法(如REINFORCE、A2C、PPO)直接优化策略,更适合连续或大规模动作空间。
# PPO (Proximal Policy Optimization) 核心算法
class PPOAgent:
def __init__(self, actor, critic, lr=3e-4, gamma=0.99, clip_eps=0.2):
self.actor = actor # 策略网络
self.critic = critic # 价值网络
self.gamma = gamma
self.clip_eps = clip_eps
self.actor_optimizer = torch.optim.Adam(actor.parameters(), lr=lr)
self.critic_optimizer = torch.optim.Adam(critic.parameters(), lr=lr)
def compute_advantage(self, rewards, values, next_value, dones):
"""计算优势函数 A(s,a) = Q(s,a) - V(s)"""
returns = []
gae = 0 # Generalized Advantage Estimation
for i in reversed(range(len(rewards))):
if dones[i]:
gae = 0
delta = rewards[i] + self.gamma * next_value * (1 - dones[i]) - values[i]
gae = delta + self.gamma * 0.95 * (1 - dones[i]) * gae
returns.insert(0, gae + values[i])
advantages = torch.tensor(returns) - torch.tensor(values)
return torch.tensor(returns), advantages
def update(self, states, actions, old_log_probs, returns, advantages):
"""PPO核心更新步骤"""
states = torch.FloatTensor(states)
actions = torch.LongTensor(actions)
old_log_probs = torch.FloatTensor(old_log_probs)
returns = torch.FloatTensor(returns)
advantages = torch.FloatTensor(advantages)
# 计算新的log概率
new_log_probs = self.compute_log_probs(states, actions)
# 计算概率比率
ratio = torch.exp(new_log_probs - old_log_probs)
# PPO裁剪目标
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - self.clip_eps, 1 + self.clip_eps) * advantages
actor_loss = -torch.min(surr1, surr2).mean()
# 价值损失
values = self.critic(states).squeeze()
critic_loss = F.mse_loss(values, returns)
# 更新网络
self.actor_optimizer.zero_grad()
actor_loss.backward()
self.actor_optimizer.step()
self.critic_optimizer.zero_grad()
critic_loss.backward()
self.critic_optimizer.step()
四、为什么AI下棋越来越强?三个关键原因
4.1 计算能力的指数级增长
从AlphaGo到AlphaZero再到绝艺、KataGo,每一次突破都伴随着算力的提升。AlphaGo用了1202个CPU和176个GPU,而AlphaZero只用了8个TPU(Google的专用AI芯片)就超越了AlphaGo。
现代RL训练一个顶级棋手通常需要数百甚至数千个GPU小时。随着云计算和专用硬件的发展,这个成本正在快速下降。
4.2 算法的持续创新
回顾强化学习的发展,几个里程碑式的技术突破:
| 年份 | 算法 | 核心创新 |
|---|---|---|
| 1951 | Wiener的乌龟机器人 | 最早的强化学习思想萌芽 |
| 1996 | TD-Gammon | 首次成功应用神经网络到棋类游戏 |
| 2013 | DQN | 经验回放+目标网络,玩 Atari 游戏 |
| 2016 | AlphaGo | MCTS+深度神经网络,击败李世石 |
| 2017 | AlphaGo Zero | 纯自我对弈,无人类知识 |
| 2017 | AlphaZero | 通用算法,同时掌握围棋、国际象棋、将棋 |
| 2020 | MuZero | 学习环境模型,无需知道规则 |
4.3 训练数据的海量增长
早期的RL算法如DQN,需要数亿帧的游戏画面才能学会玩 Atari 游戏。AlphaGo Zero通过自我对弈产生了约4900万局棋谱,远远超过人类历史上所有的围棋对局记录。
现在,像 katago 这样的开源围棋AI,可以轻松地生成数千万局的训练数据,这些数据让模型能覆盖几乎所有的棋盘局面。
五、从零开始学强化学习:实战项目指南
如果你想真正掌握强化学习,光看理论是不够的。下面我带你从最基础的项目开始,一步步构建知识体系。
5.1 第一步:用Q-Learning解决迷宫问题
让我们从一个经典的学习项目开始——让AI学会走出迷宫。
import numpy as np
import random
class MazeSolver:
def __init__(self, maze_size=5, learning_rate=0.1, discount_factor=0.95,
epsilon=1.0, epsilon_decay=0.995, epsilon_min=0.01):
self.maze_size = maze_size
self.lr = learning_rate
self.gamma = discount_factor
self.epsilon = epsilon
self.epsilon_decay = epsilon_decay
self.epsilon_min = epsilon_min
# 定义迷宫: 0=空地, 1=墙, 2=起点, 3=终点
self.maze = np.array([
[2, 0, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 0, 3]
])
# Q表:状态-动作的值
self.q_table = np.zeros((maze_size, maze_size, 4)) # 4个动作:上、下、左、右
def get_state(self, pos):
"""获取当前状态"""
return pos
def get_valid_actions(self, pos):
"""获取合法动作"""
row, col = pos
actions = []
# 上
if row > 0 and self.maze[row-1][col] != 1:
actions.append(0)
# 下
if row < self.maze_size-1 and self.maze[row+1][col] != 1:
actions.append(1)
# 左
if col > 0 and self.maze[row][col-1] != 1:
actions.append(2)
# 右
if col < self.maze_size-1 and self.maze[row][col+1] != 1:
actions.append(3)
return actions
def choose_action(self, pos):
"""ε-贪心策略选择动作"""
if random.random() < self.epsilon:
# 探索:随机选择
return random.choice(self.get_valid_actions(pos))
else:
# 利用:选择Q值最高的动作
row, col = pos
valid_actions = self.get_valid_actions(pos)
q_values = [self.q_table[row, col, a] for a in valid_actions]
best_action = valid_actions[np.argmax(q_values)]
return best_action
def step(self, pos, action):
"""执行动作,返回新状态和奖励"""
row, col = pos
# 根据动作移动
if action == 0: # 上
new_pos = (row - 1, col)
elif action == 1: # 下
new_pos = (row + 1, col)
elif action == 2: # 左
new_pos = (row, col - 1)
else: # 右
new_pos = (row, col + 1)
# 检查是否撞到墙
if self.maze[new_pos] == 1:
return pos, -10, False # 撞墙,惩罚
# 检查是否到达终点
if self.maze[new_pos] == 3:
return new_pos, 100, True # 到达终点,奖励
# 普通移动
return new_pos, -1, False
def train(self, episodes=1000):
"""训练Q-LearningAgent"""
for episode in range(episodes):
# 找到起点
start_pos = np.argwhere(self.maze == 2)[0]
current_pos = tuple(start_pos)
while True:
action = self.choose_action(current_pos)
next_pos, reward, done = self.step(current_pos, action)
# Q-Learning更新公式
current_q = self.q_table[current_pos[0], current_pos[1], action]
if done:
target_q = reward
else:
next_max_q = np.max(self.q_table[next_pos[0], next_pos[1]])
target_q = reward + self.gamma * next_max_q
# 更新Q值
self.q_table[current_pos[0], current_pos[1], action] = (
current_q + self.lr * (target_q - current_q)
)
current_pos = next_pos
if done:
break
# 衰减探索率
self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)
if episode % 100 == 0:
print(f"Episode {episode}, Epsilon: {self.epsilon:.3f}")
def solve(self):
"""显示最优路径"""
path = []
pos = tuple(np.argwhere(self.maze == 2)[0])
path.append(pos)
while self.maze[pos] != 3:
row, col = pos
valid_actions = self.get_valid_actions(pos)
q_values = [self.q_table[row, col, a] for a in valid_actions]
best_action = valid_actions[np.argmax(q_values)]
pos = self.step(pos, best_action)[0]
path.append(pos)
return path
# 运行训练
solver = MazeSolver()
solver.train(episodes=2000)
path = solver.solve()
print(f"最优路径: {path}")
5.2 第二步:用Deep Q-Learning玩CartPole
CartPole是OpenAI Gym中最经典的强化学习环境——让一根杆子保持直立。
import gym
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
class DQN(nn.Module):
def __init__(self, input_dim, output_dim):
super(DQN, self).__init__()
self.network = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, output_dim)
)
def forward(self, x):
return self.network(x)
class DQNAgent:
def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99,
epsilon=1.0, epsilon_decay=0.995, epsilon_min=0.01):
self.state_dim = state_dim
self.action_dim = action_dim
self.gamma = gamma
self.epsilon = epsilon
self.epsilon_decay = epsilon_decay
self.epsilon_min = epsilon_min
# 神经网络
self.q_network = DQN(state_dim, action_dim)
self.target_network = DQN(state_dim, action_dim)
self.target_network.load_state_dict(self.q_network.state_dict())
# 优化器
self.optimizer = optim.Adam(self.q_network.parameters(), lr=lr)
# 经验回放缓冲区
self.memory = []
self.batch_size = 64
def select_action(self, state):
if np.random.rand() < self.epsilon:
return np.random.randint(self.action_dim)
state = torch.FloatTensor(state).unsqueeze(0)
with torch.no_grad():
q_values = self.q_network(state)
return q_values.argmax(dim=1).item()
def store_transition(self, state, action, reward, next_state, done):
self.memory.append((state, action, reward, next_state, done))
def learn(self):
if len(self.memory) < self.batch_size:
return
# 随机采样
batch = random.sample(self.memory, self.batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
states = torch.FloatTensor(np.array(states))
actions = torch.LongTensor(actions)
rewards = torch.FloatTensor(rewards)
next_states = torch.FloatTensor(np.array(next_states))
dones = torch.FloatTensor(dones)
# 计算当前Q值
current_q_values = self.q_network(states).gather(1, actions.unsqueeze(1)).squeeze(1)
# 计算目标Q值
with torch.no_grad():
next_q_values = self.target_network(next_states).max(1)[0]
target_q_values = rewards + self.gamma * next_q_values * (1 - dones)
# 计算损失并更新
loss = nn.MSELoss()(current_q_values, target_q_values)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 更新目标网络
self.target_network.load_state_dict(self.q_network.state_dict())
# 衰减epsilon
self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)
def train(self, env, episodes=500):
for episode in range(episodes):
state = env.reset()[0]
total_reward = 0
while True:
action = self.select_action(state)
next_state, reward, done, truncated, _ = env.step(action)
self.store_transition(state, action, reward, next_state, done)
self.learn()
state = next_state
total_reward += reward
if done:
break
if episode % 50 == 0:
print(f"Episode {episode}, Total Reward: {total_reward:.2f}, Epsilon: {self.epsilon:.3f}")
if total_reward >= 195: # CartPole的成功标准
print(f"成功!在 Episode {episode} 达到了目标")
break
# 运行训练
env = gym.make('CartPole-v1')
agent = DQNAgent(state_dim=4, action_dim=2)
agent.train(env, episodes=500)
5.3 第三步:用PPO训练一个走迷宫的AI
PPO是目前最稳定、最常用的策略梯度算法之一。
import gym
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
class ActorCritic(nn.Module):
def __init__(self, state_dim, action_dim):
super(ActorCritic, self).__init__()
self.actor = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, action_dim),
nn.Softmax(dim=-1)
)
self.critic = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 1)
)
def act(self, state):
state = torch.FloatTensor(state).unsqueeze(0)
action_probs = self.actor(state)
action_dist = torch.distributions.Categorical(action_probs)
action = action_dist.sample()
return action.item(), action_dist.log_prob(action)
def get_value(self, state):
state = torch.FloatTensor(state).unsqueeze(0)
return self.critic(state).item()
class PPOAgent:
def __init__(self, state_dim, action_dim, lr=3e-4, gamma=0.99,
clip_eps=0.2, epochs=10, batch_size=64):
self.gamma = gamma
self.clip_eps = clip_eps
self.epochs = epochs
self.batch_size = batch_size
self.policy = ActorCritic(state_dim, action_dim)
self.optimizer = optim.Adam(self.policy.parameters(), lr=lr)
self.states = []
self.actions = []
self.log_probs = []
self.rewards = []
def store_transition(self, state, action, log_prob, reward):
self.states.append(state)
self.actions.append(action)
self.log_probs.append(log_prob)
self.rewards.append(reward)
def compute_gae(self, values, rewards, dones, last_value):
"""Generalized Advantage Estimation (GAE)"""
advantages = []
gae = 0
for t in reversed(range(len(rewards))):
if t == len(rewards) - 1:
next_value = last_value
else:
next_value = values[t + 1]
delta = rewards[t] + self.gamma * next_value * (1 - dones[t]) - values[t]
gae = delta + self.gamma * 0.95 * (1 - dones[t]) * gae
advantages.insert(0, gae)
returns = np.array(advantages) + np.array(values)
return advantages, returns
def update(self):
states = torch.FloatTensor(np.array(self.states))
actions = torch.LongTensor(self.actions)
old_log_probs = torch.FloatTensor(self.log_probs)
rewards = torch.FloatTensor(self.rewards)
# 计算优势函数和返回值
values = np.array([self.policy.get_value(s) for s in self.states])
advantages, returns = self.compute_gae(values, rewards, [0]*len(rewards), values[-1])
advantages = torch.FloatTensor(advantages)
returns = torch.FloatTensor(returns)
# PPO更新
for _ in range(self.epochs):
# 计算新的log概率
action_probs = self.policy.actor(states)
dist = torch.distributions.Categorical(action_probs)
new_log_probs = dist.log_prob(actions)
# 计算概率比率
ratio = torch.exp(new_log_probs - old_log_probs)
# PPO裁剪目标
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - self.clip_eps, 1 + self.clip_eps) * advantages
actor_loss = -torch.min(surr1, surr2).mean()
# 价值损失
current_values = self.policy.critic(states).squeeze()
critic_loss = nn.MSELoss()(current_values, returns)
# 总损失
loss = actor_loss + 0.5 * critic_loss
# 更新
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 清空缓冲区
self.states = []
self.actions = []
self.log_probs = []
self.rewards = []
def train(self, env, episodes=500):
for episode in range(episodes):
state = env.reset()[0]
total_reward = 0
while True:
action, log_prob = self.policy.act(state)
next_state, reward, done, truncated, _ = env.step(action)
self.store_transition(state, action, log_prob, reward)
total_reward += reward
state = next_state
if done:
self.update()
break
if episode % 50 == 0:
print(f"Episode {episode}, Total Reward: {total_reward:.2f}")
六、实战案例:从零训练一个五子棋AI
下面是一个完整的五子棋AI训练示例,展示了如何应用强化学习。
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import random
class GomokuBoard:
"""五子棋棋盘"""
def __init__(self, size=15):
self.size = size
self.board = np.zeros((size, size), dtype=int)
self.current_player = 1 # 1=黑棋, 2=白棋
def get_valid_moves(self):
"""获取所有合法落子位置"""
moves = []
for i in range(self.size):
for j in range(self.size):
if self.board[i, j] == 0:
# 检查周围是否有棋子(简化规则)
if i > 0 and self.board[i-1, j] != 0:
moves.append((i, j))
elif i < self.size-1 and self.board[i+1, j] != 0:
moves.append((i, j))
elif j > 0 and self.board[i, j-1] != 0:
moves.append((i, j))
elif j < self.size-1 and self.board[i, j+1] != 0:
moves.append((i, j))
return moves
def make_move(self, move):
"""落子"""
i, j = move
self.board[i, j] = self.current_player
self.current_player = 3 - self.current_player # 切换玩家
def check_win(self, player):
"""检查是否获胜"""
# 检查四个方向:水平、垂直、两条对角线
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
for i in range(self.size):
for j in range(self.size):
if self.board[i, j] != player:
continue
for di, dj in directions:
count = 1
# 正方向
for k in range(1, 5):
ni, nj = i + di * k, j + dj * k
if 0 <= ni < self.size and 0 <= nj < self.size and self.board[ni, nj] == player:
count += 1
else:
break
# 反方向
for k in range(1, 5):
ni, nj = i - di * k, j - dj * k
if 0 <= ni < self.size and 0 <= nj < self.size and self.board[ni, nj] == player:
count += 1
else:
break
if count >= 5:
return True
return False
def is_game_over(self):
"""检查游戏是否结束"""
if self.check_win(1):
return True, 1
if self.check_win(2):
return True, 2
if len(self.get_valid_moves()) == 0:
return True, 0 # 平局
return False, 0
def get_state(self):
"""获取状态表示(用于神经网络输入)"""
# 用两个15x15的矩阵分别表示黑白棋的位置
state = np.zeros((2, self.size, self.size))
state[0] = (self.board == 1).astype(float)
state[1] = (self.board == 2).astype(float)
return state
class GomokuPolicy(nn.Module):
"""五子棋策略网络"""
def __init__(self, board_size=15):
super(GomokuPolicy, self).__init__()
self.board_size = board_size
self.conv1 = nn.Conv2d(2, 64, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(64)
self.conv2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.policy_head = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * board_size * board_size, 1024),
nn.ReLU(),
nn.Linear(1024, board_size * board_size)
)
self.value_head = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * board_size * board_size, 256),
nn.ReLU(),
nn.Linear(256, 1),
nn.Tanh()
)
def forward(self, state):
x = F.relu(self.bn1(self.conv1(state)))
x = F.relu(self.bn2(self.conv2(x)))
policy = self.policy_head(x)
value = self.value_head(x)
return policy, value
class GomokuAgent:
def __init__(self, learning_rate=1e-4, gamma=0.99, epsilon=1.0):
self.gamma = gamma
self.epsilon = epsilon
self.policy = GomokuPolicy()
self.optimizer = torch.optim.Adam(self.policy.parameters(), lr=learning_rate)
self.memory = []
self.batch_size = 32
def select_action(self, board):
"""选择动作"""
valid_moves = board.get_valid_moves()
if len(valid_moves) == 0:
return None
# ε-贪心策略
if random.random() < self.epsilon:
return random.choice(valid_moves)
# 使用策略网络
state = board.get_state().unsqueeze(0)
with torch.no_grad():
policy, _ = self.policy(state)
policy = F.softmax(policy, dim=-1)
# 只考虑合法动作
policy_values = torch.zeros(self.policy.board_size * self.policy.board_size)
for i, j in valid_moves:
policy_values[i * self.policy.board_size + j] = policy[0, i * self.policy.board_size + j]
# 概率采样
probabilities = policy_values / policy_values.sum()
move_idx = torch.multinomial(probabilities, 1).item()
return (move_idx // self.policy.board_size, move_idx % self.policy.board_size)
def store_transition(self, state, action, reward, next_state, done):
self.memory.append({
'state': state,
'action': action,
'reward': reward,
'next_state': next_state,
'done': done
})
def train(self):
if len(self.memory) < self.batch_size:
return
# 采样一批经验
batch = random.sample(self.memory, self.batch_size)
states = torch.stack([torch.FloatTensor(b['state']) for b in batch])
actions = torch.LongTensor([b['action'][0] * self.policy.board_size + b['action'][1]
for b in batch])
rewards = torch.FloatTensor([b['reward'] for b in batch])
next_states = torch.stack([torch.FloatTensor(b['next_state']) for b in batch])
dones = torch.FloatTensor([1.0 if b['done'] else 0.0 for b in batch])
# 计算当前Q值
current_policy, current_value = self.policy(states)
current_q = current_policy.gather(1, actions.unsqueeze(1)).squeeze(1)
# 计算目标Q值
with torch.no_grad():
_, next_value = self.policy(next_states)
target_q = rewards + self.gamma * next_value * (1 - dones)
# 计算损失
loss = F.mse_loss(current_q, target_q)
# 更新网络
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
def train_self_play(self, num_games=100):
"""自我对弈训练"""
for game in range(num_games):
board = GomokuBoard()
states_actions_rewards = []
while True:
state = board.get_state()
action = self.select_action(board)
board.make_move(action)
done, winner = board.is_game_over()
# 记录状态
states_actions_rewards.append({
'state': state,
'action': action,
'winner': winner,
'is_current_player': True if board.current_player == 1 else False
})
if done:
break
# 计算奖励(赢家得1分,输家得-1分,平局得0分)
for i, item in enumerate(states_actions_rewards):
if item['winner'] == 1:
reward = 1 if not item['is_current_player'] else -1
elif item['winner'] == 2:
reward = -1 if not item['is_current_player'] else 1
else:
reward = 0
self.store_transition(
item['state'],
item['action'],
reward,
states_actions_rewards[min(i+1, len(states_actions_rewards)-1)]['state'],
item['winner'] != 0
)
if (game + 1) % 10 == 0:
self.train()
print(f"训练了 {game + 1} 局游戏")
# 衰减epsilon
self.epsilon = max(0.1, self.epsilon * 0.999)
# 训练Agent
agent = GomokuAgent()
agent.train_self_play(num_games=500)
七、强化学习在更多领域的应用
强化学习不只是用于下棋,它在很多领域都有出色表现:
7.1 游戏领域
- Atari游戏:DQN首次证明AI可以学会玩多种 Atari 游戏
- 星际争霸2:DeepMind的AlphaStar击败了职业玩家
- Dota 2:OpenAI Five在5v5中击败了人类世界冠军队伍
- 围棋:AlphaGo Zero、AlphaZero、绝艺、KataGo等
7.2 机器人控制
- 机械臂抓取物体
- 机器人行走平衡
- 自动驾驶车辆
7.3 资源管理
- 数据中心冷却系统优化
- 电网调度
- 推荐系统
7.4 化学与生物
- 蛋白质折叠预测
- 分子设计
- 药物发现
八、学习强化学习的最佳路径建议
如果你想系统学习强化学习,我建议按照以下路径:
第一阶段:基础准备
- 掌握Python编程
- 理解基础的机器学习概念(监督学习、无监督学习)
- 学习PyTorch或TensorFlow框架
第二阶段:核心概念
- 理解马尔可夫决策过程(MDP)
- 掌握贝尔曼方程
- 学习策略梯度定理
第三阶段:经典算法
- Q-Learning
- Deep Q-Network (DQN)
- Policy Gradient (REINFORCE)
- Actor-Critic方法
- PPO、A2C、TRPO
第四阶段:进阶学习
- 多智能体强化学习
- 模型基于强化学习
- 层次强化学习
- 模仿学习
推荐资源:
- 书籍:《强化学习》(Sutton & Barto)
- 课程:伯克利大学的CS285、吴恩达的强化学习课程
- 库:Gymnasium、Stable Baselines3、Ray RLlib
九、结语:AI下棋的强大只是开始
从AlphaGo到现在的各类棋类AI,我们见证的是一场从”弱人工智能”向”强人工智能”迈进的过程。强化学习让AI学会了”自我学习”——不需要人类的标签数据,只需要规则和奖励信号,AI就能通过不断试错找到最优策略。
但AI下棋的强大并不意味着AI理解了游戏。AlphaGo的那些”神之一手”,很多时候是人类棋手从未想过的走法,但这背后是数以亿计的自我对弈和数学优化,而不是人类意义上的”创造力”。
如果你对强化学习感兴趣,最好的方式就是动手实践。从简单的迷宫问题开始,逐步挑战更复杂的任务。记住,强化学习是一个需要耐心和实践的领域,每一次失败都是学习的一部分。
正如一位 reinforcement learning 研究者所说:”在强化学习中,没有失败,只有反馈。”
