还记得2016年3月AlphaGo与李世石的那场世纪对决吗?当时的第37手让全世界为之震惊——这一步看似错误的棋,后来却被誉为天才之举。这就是强化学习的魅力所在。
最近重看了《AlphaGo》纪录片,再次被这种学习方式深深打动。可怕的是,AlphaGo并没有从数据库、规则或策略书中学习棋艺,而是通过数百万次自我对弈,在实践中学会了如何获胜。
什么是强化学习?
观察婴儿学走路的过程:站起来,摔倒,再次尝试——最终迈出了第一步。
没有老师教他们如何做,婴儿完全是通过试错来学习走路的。当他们能够站立或走几步时,这对他们来说就是奖励。毕竟,他们的目标是能够走路。如果摔倒了,就没有奖励。
这种试错和奖励的学习过程,正是强化学习(RL)的基本思想。
强化学习是一种学习方法,智能体通过与环境交互来学习哪些行动能够带来奖励。
其目标是:长期获得尽可能多的奖励。
- 与监督学习不同,这里没有"正确答案"或标签。智能体必须自己找出哪些决策是好的。
- 与无监督学习不同,目标不是发现数据中的隐藏模式,而是执行那些能最大化奖励的行动。
强化学习智能体如何思考、决策和学习
要让强化学习智能体学习,需要四个要素:对当前位置的认知(状态)、可以执行的操作(动作)、想要达成的目标(奖励)以及过去策略的表现如何(价值)。
智能体行动,获得反馈,然后改进。
为了实现这一点,需要四个核心组件:
1. 策略/战略
这是智能体在特定状态下决定执行哪个动作的规则或策略。在简单情况下,这是一个查找表。在更复杂的应用中(例如使用神经网络),它是一个函数。
2. 奖励信号
奖励是来自环境的反馈。例如,胜利可以是+1,平局是0,失败是-1。智能体的目标是在尽可能多的步骤中收集尽可能多的奖励。
3. 价值函数
这个函数估计一个状态的预期未来奖励。奖励显示智能体该动作是"好"还是"坏"。价值函数估计一个状态有多好——不仅仅是当前,而是考虑智能体从该状态开始可以期待的未来奖励。因此,价值函数估计的是一个状态的长期利益。
4. 环境模型
模型告诉智能体:“如果我在状态S中执行动作A,我可能会到达状态S’并获得奖励R。”
不过,在Q-learning等无模型方法中,这并不是必需的。
利用与探索:第37手的启示
你可能还记得AlphaGo与李世石第二局中的第37手:
这是一个看起来像错误的不寻常走法——但后来被誉为天才之举。
算法为什么要这样做?
计算机程序在尝试新的东西。这叫做探索。
强化学习需要两者兼顾:智能体必须在利用和探索之间找到平衡。
- 利用意味着智能体使用它已经知道的动作。
- 探索则是智能体第一次尝试的动作。它尝试这些动作是因为它们可能比已知的动作更好。
智能体通过试错来寻找最优策略。
用强化学习玩井字棋
让我们通过一个大家都很熟悉的游戏来看看强化学习的实际应用。
你可能小时候也玩过:井字棋。
这个游戏非常适合作为入门例子,因为它不需要神经网络,规则清晰,我们只需要一点Python代码就能实现:
- 我们的智能体从对游戏的零知识开始。它就像第一次看到这个游戏的人类一样。
- 智能体逐渐评估每个游戏情况:0.5分意味着"我还不知道在这里是否会获胜",1.0意味着"这种情况几乎肯定会胜利"。
- 通过玩很多局游戏,智能体观察什么有效——并调整其策略。
目标是:在每一轮中,智能体应该选择能带来最高长期奖励的行动。
在这一部分,我们将逐步构建这样一个强化学习系统,创建TicTacToeRL.py
文件。
→ 你可以在这个GitHub仓库中找到所有代码。
1. 构建游戏环境
在强化学习中,智能体通过与环境的交互来学习。环境决定什么是状态(例如当前棋盘)、允许哪些动作(例如你可以在哪里下棋)以及对动作有什么反馈(例如获胜奖励+1)。
理论上,我们称这种设置为马尔可夫决策过程:模型由状态、动作和奖励组成。
首先,我们创建一个TicTacToe
类。这管理游戏棋盘(我们创建为3×3的NumPy数组)并管理游戏逻辑:
reset(self)
函数开始新游戏。
available_actions()
函数返回所有空位。
step(self, action, player)
函数执行游戏移动。这里我们返回新状态、奖励(1=获胜,0.5=平局,-10=无效移动)和游戏状态。在这个例子中,我们对无效移动给予-10的重罚,以便智能体快速学会避免它们——这是小型强化学习环境中的常见技巧。
check_winner()
函数检查玩家是否连成三个X或O并因此获胜。
- 用
render_gui()
我们用matplotlib将当前棋盘显示为X和O图形。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import random
from collections import defaultdict
# 井字棋游戏环境
class TicTacToe:
def __init__(self):
self.board = np.zeros((3, 3), dtype=int)
self.done = False
self.winner = None
def reset(self):
self.board[:] = 0
self.done = False
self.winner = None
return self.get_state()
def get_state(self):
return tuple(self.board.flatten())
def available_actions(self):
return [(i, j) for i in range(3) for j in range(3) if self.board[i, j] == 0]
def step(self, action, player):
if self.done:
raise ValueError("游戏已结束")
i, j = action
if self.board[i, j] != 0:
return self.get_state(), -10, True
self.board[i, j] = player
if self.check_winner(player):
self.done = True
self.winner = player
return self.get_state(), 1, True
elif not self.available_actions():
self.done = True
return self.get_state(), 0.5, True
return self.get_state(), 0, False
def check_winner(self, player):
for i in range(3):
if all(self.board[i, :] == player) or all(self.board[:, i] == player):
return True
if all(np.diag(self.board) == player) or all(np.diag(np.fliplr(self.board)) == player):
return True
return False
def render_gui(self):
fig, ax = plt.subplots()
ax.set_xticks([0.5, 1.5], minor=False)
ax.set_yticks([0.5, 1.5], minor=False)
ax.set_xticks([], minor=True)
ax.set_yticks([], minor=True)
ax.set_xlim(-0.5, 2.5)
ax.set_ylim(-0.5, 2.5)
ax.grid(True, which='major', color='black', linewidth=2)
for i in range(3):
for j in range(3):
value = self.board[i, j]
if value == 1:
ax.plot(j, 2 - i, 'x', markersize=20, markeredgewidth=2, color='blue')
elif value == -1:
circle = plt.Circle((j, 2 - i), 0.3, fill=False, color='red', linewidth=2)
ax.add_patch(circle)
ax.set_aspect('equal')
plt.axis('off')
plt.show()
|
2. 编程Q-learning智能体
接下来,我们定义学习部分:我们的智能体。
它决定在特定状态下执行哪个动作以获得尽可能多的奖励。
智能体使用经典的强化学习方法Q-learning。为每个状态和动作的组合存储一个Q值——该动作的估计长期收益。
最重要的方法是:
- 使用
choose_action(self, state, actions)
函数,智能体在每个游戏情况下决定是选择它已经熟知的动作(利用)还是尝试尚未充分测试的新动作(探索)。
这个决策基于所谓的ε-贪婪方法:
以ε = 0.1的概率,智能体选择随机动作(探索),
以90%的概率(1 – ε),它基于Q表选择当前已知的最佳动作(利用)。
- 通过
update(state, action, reward, next_state, next_actions)
函数,我们根据动作的好坏和后续发生的情况调整Q值。这是智能体的核心学习步骤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# Q-learning智能体
class QLearningAgent:
def __init__(self, alpha=0.1, gamma=0.9, epsilon=0.1):
self.q_table = defaultdict(float)
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # 探索率
def get_q(self, state, action):
return self.q_table[(state, action)]
def choose_action(self, state, actions):
if random.random() < self.epsilon:
return random.choice(actions)
else:
q_values = [self.get_q(state, a) for a in actions]
max_q = max(q_values)
best_actions = [a for a, q in zip(actions, q_values) if q == max_q]
return random.choice(best_actions)
def update(self, state, action, reward, next_state, next_actions):
max_q_next = max([self.get_q(next_state, a) for a in next_actions], default=0)
old_value = self.q_table[(state, action)]
new_value = old_value + self.alpha * (reward + self.gamma * max_q_next - old_value)
self.q_table[(state, action)] = new_value
|
3. 训练智能体
实际的学习过程从这一步开始。在训练期间,智能体通过试错学习。智能体玩许多游戏,记住哪些动作效果好——并调整其策略。
在训练过程中,智能体学习其动作如何获得奖励,其行为如何影响后续状态以及长期更好的策略如何发展。
- 通过
train(agent, episodes=10000)
函数,我们定义智能体对抗简单的随机对手玩10,000局游戏。
在每一局中,智能体(玩家1)先移动,然后是对手(玩家2)。每次移动后,智能体通过update()
学习。
- 每1000局游戏我们保存有多少胜利、平局和失败。
- 最后,我们用matplotlib绘制学习曲线。它显示智能体如何随时间改进。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# 训练与学习曲线
def train(agent, episodes=10000):
env = TicTacToe()
results = {"win": 0, "draw": 0, "loss": 0}
win_rates = []
draw_rates = []
loss_rates = []
for episode in range(episodes):
state = env.reset()
done = False
while not done:
actions = env.available_actions()
action = agent.choose_action(state, actions)
next_state, reward, done = env.step(action, player=1)
if done:
agent.update(state, action, reward, next_state, [])
if reward == 1:
results["win"] += 1
elif reward == 0.5:
results["draw"] += 1
else:
results["loss"] += 1
break
opp_actions = env.available_actions()
opp_action = random.choice(opp_actions)
next_state2, reward2, done = env.step(opp_action, player=-1)
if done:
agent.update(state, action, -1 * reward2, next_state2, [])
if reward2 == 1:
results["loss"] += 1
elif reward2 == 0.5:
results["draw"] += 1
else:
results["win"] += 1
break
next_actions = env.available_actions()
agent.update(state, action, reward, next_state2, next_actions)
state = next_state2
if (episode + 1) % 1000 == 0:
total = sum(results.values())
win_rates.append(results["win"] / total)
draw_rates.append(results["draw"] / total)
loss_rates.append(results["loss"] / total)
print(f"第{episode+1}局: 胜利 {results['win']}, 平局 {results['draw']}, 失败 {results['loss']}")
results = {"win": 0, "draw": 0, "loss": 0}
x = [i * 1000 for i in range(1, len(win_rates) + 1)]
plt.plot(x, win_rates, label="胜率")
plt.plot(x, draw_rates, label="平局率")
plt.plot(x, loss_rates, label="败率")
plt.xlabel("训练局数")
plt.ylabel("比率")
plt.title("Q-learning智能体学习曲线")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
|
4. 棋盘可视化
通过主程序if __name__ == "__main__":
我们定义程序的起点。它确保当我们执行脚本时智能体的训练自动运行。我们使用render_gui()
方法将井字棋棋盘显示为图形。
1
2
3
4
5
6
7
8
9
10
|
# 主程序
if __name__ == "__main__":
agent = QLearningAgent()
train(agent, episodes=10000)
# 示例棋盘可视化
env = TicTacToe()
env.board[0, 0] = 1
env.board[1, 1] = -1
env.render_gui()
|
终端执行
我们将代码保存为TicTacToeRL.py
文件。
在终端中,我们导航到存储TicTacToeRL.py
的相应目录,并使用命令python TicTacToeRL.py
执行文件。
在终端中,我们可以看到每1000局后我们的智能体赢了多少局游戏,在可视化中我们看到学习曲线显示智能体的改进过程。
总结思考
通过井字棋,我们使用一个简单的游戏和一些Python代码——但我们可以清楚地看到强化学习是如何工作的:
- 智能体从没有任何先验知识开始。
- 它通过反馈和经验制定策略。
- 因此,它的决策逐渐改善——不是因为它知道规则,而是因为它学习了。
在我们的例子中,对手是一个随机智能体。接下来,我们可以看看我们的Q-learning智能体如何对抗另一个学习智能体或对抗我们自己的表现。
强化学习向我们展示,机器智能不仅通过知识或信息创造——而是通过经验、反馈和适应。
进一步学习资源
强化学习的魅力在于,它让我们看到了机器如何像人类一样从错误中学习,并最终超越人类的表现。正如AlphaGo的第37手所证明的那样,有时候最好的策略就是勇于探索未知。