在Q学习中应用含参数函数实现价值近似

  |  

摘要: 在 Q 学习中应用含参数函数实现价值近似

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


前面我们系统学习了强化学习的各种基础算法,总结可以参考文章 强化学习方法的分类总结

其中 Q 学习、蒙特卡洛方法、SARSA、Actor Critic 等方法我们都有实现过代码模板:

其中每个方法的代码中都有一个 Q[s][a], 其中保存的是各种状态下各种行动的价值。

Q 学习基于价值近似在选择行动的时候,采取使得价值最大的行动来进行 TD 方法。本文我们探讨如何通过含参数函数实现价值近似

这里的含参数函数就是价值函数,它是进行价值近似的函数。价值函数近似指的是学习价值函数的过程。

我们根据 使用神经网络实现强化学习的框架 首先创建一个根据价值函数选择行动的智能体,ValueFunctionAgent,然后将整个框架用于 gym 的 CartPole 中。而价值函数将使用神经网络,参考 极简神经网络-深度强化学习预备知识


CartPole 环境

状态: 小车的位置、加速度、杆的角度、杆倒下的角速度。四个标量值。

行动: 向左、向右。

奖励: 始终为 1,如果杆子倒下,则回合结束。


环境中的 Agent 的实现

我们看一下在 CartPole 中的智能体的实现。首先给出完整代码。其中 FNAgent, Trainer, Observer使用神经网络实现强化学习的框架 中的组件。

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
import random
import argparse
import numpy as np
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import joblib
import gym


class ValueFunctionAgent(FNAgent):

def save(self, model_path):
joblib.dump(self.model, model_path)

@classmethod
def load(cls, env, model_path, epsilon=0.0001):
actions = list(range(env.action_space.n))
agent = cls(epsilon, actions)
agent.model = joblib.load(model_path)
agent.initialized = True
return agent

def initialize(self, experiences):
scaler = StandardScaler()
estimator = MLPRegressor(hidden_layer_sizes=(10, 10), max_iter=1)
self.model = Pipeline([("scaler", scaler), ("estimator", estimator)])

states = np.vstack([e.s for e in experiences])
self.model.named_steps["scaler"].fit(states)

# 避免在学习之前进行预测
self.update([experiences[0]], gamma=0)
self.initialized = True
print("Done initialization. From now, begin training!")

def estimate(self, s):
estimated = self.model.predict(s)[0]
return estimated

def _predict(self, states):
if self.initialized:
predicteds = self.model.predict(states)
else:
size = len(self.actions) * len(states)
predicteds = np.random.uniform(size=size)
predicteds = predicteds.reshape((-1, len(self.actions)))
return predicteds

def update(self, experiences, gamma):
states = np.vstack([e.s for e in experiences])
n_states = np.vstack([e.n_s for e in experiences])

estimateds = self._predict(states)
future = self._predict(n_states)

for i, e in enumerate(experiences):
reward = e.r
if not e.d:
reward += gamma * np.max(future[i])
estimateds[i][e.a] = reward

estimateds = np.array(estimateds)
states = self.model.named_steps["scaler"].transform(states)
self.model.named_steps["estimator"].partial_fit(states, estimateds)
  • MLPRegressor(hidden_layer_sizes=(10, 10), max_iter=1) 是重叠了 2 个 10 节点的隐藏层的神经网络。
  • MLPRegressor 接受状态,返回该状态下各种行动的价值。
  • 输入状态时,最好进行归一化 StandardScaler。
  • 将 StandardScaler 与 MLPRegressor 连接成的 Pipeline 作为模型。
  • StandardScaler 在 initialize 函数中获取的经验 experiences 中的状态下进行初始化。

update(experiences, gamma) 是用于更新参数的,上图是示意图,这是使用含参数函数实现价值近似的关键组件。一些要点如下。

  • initialize 中,self.update([experiences[0]], gamma=0) 这一处理是为例避免在学习之前就进行预测而引发异常。
  • update 的实现与 Q 学习相同,预测结果在 estimateds 中。
  • 仅当存在下一个迁移目标时,才生成迁移后的价值,并采取价值最大化的行动 (np.max(future[i]))。
  • 更新之后的差值就是 TD 误差,通过 partial_fit 调整参数,使该误差减小。
  • 如果用 fit 而不是 partial_fit,则到目前为止的学习结果将被重置,并从头开始学习。
  • estimateds 是纵向为状态、横向为行动的表格,与此前的 Q 表格很类似。
  • 与基于 Q 表格的算法那的不同,TD 误差用于更新价值函数的参数,而不是更新 Q 表格

处理 CartPole 环境的 Observer

1
2
3
class CartObserver(Observer):
def transform(self, state):
return np.array(state).reshape((1, -1))

学习的 Trainer

准备学习时(begin_train),进行智能体的初始化(agent.initialize),然后逐步进行学习(agent.update)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ValueFunctionTrainer(Trainer):

def train(self, env, episode_count=220, epsilon=0.1, initial_count=-1,
render=False):
actions = list(range(env.action_space.n))
agent = ValueFunctionAgent(epsilon, actions)
self.train_loop(env, agent, episode_count, initial_count, render)
return agent

def begin_train(self, episode, agent):
agent.initialize(self.experiences)

def step(self, episode, step_count, agent, experience):
if self.training:
batch = random.sample(self.experiences, self.batch_size)
agent.update(batch, self.gamma)

def episode_end(self, episode, step_count, agent):
rewards = [e.r for e in self.get_recent(step_count)]
self.reward_log.append(sum(rewards))

if self.is_event(episode, self.report_interval):
recent_rewards = self.reward_log[-self.report_interval:]
self.logger.describe("reward", recent_rewards, episode=episode)

至此我们实现完了学习的过程。

此后通过 Agent 的 play 方法可以看学习完的模型进行预测的动作。


训练和预测

使用含参数函数进行价值近似的示意图如下:

通过 Trainer 中的 begin_train 和 step 中分别调用了智能体的初始化和学习的过程。

通过 Trainer 调用的智能体的初始化和学习过程具体如下:

下面是训练和预测的代码:

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
def main(play):
env = gym.make("CartPole-v0")
env_observer = CartPoleObserver(env)
trainer = ValueFunctionTrainer()
path = trainer.logger.path_of("value_function_agent.pkl")

if play:
agent = ValueFunctionAgent.load(env_observer, path)
agent.play(env_observer)
else:
trained = trainer.train(env_observer)
trainer.logger.plot("Rewards", trainer.reward_log,
trainer.report_interval)
trained.save(path)

time.sleep(3)
env.close()


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="VF Agent")
parser.add_argument("--play", action="store_true",
help="play with trained model")

args = parser.parse_args()
main(args.play)

训练的结果如下


推理的结果如下


Share