动手学习强化学习 强化学习这门课程要求写一个AI进行五子棋,我找了个Alpha zero的算法 但我其实并不太明白的
所以说我又找了一下深度强化学习的资料,看到一篇知乎 里的评论给了很多很多的学习资料
然后我就选择其中一本动手学习深度学习 这本书,感觉是和dive into deep learning是差不多的类型
等我花一周时间拿下这本书
多臂老虎机 多臂老虎机是相当简化的强化学习问题(但我觉得其实也可以非常复杂),里面只有动作和价值。 问题主题就是有K个老虎机,你摆动其中的摇杆,然后获得奖励,问题核心在于如何动作获得最大的价值, 在一定时间步内的动作要想获取最大价值,就需要每次动作都要在最能(概率最大的)获得更多奖励的老虎机上扳动摇杆 然后我们先初始化一下老虎机
1 2 3 4 5 6 7 8 9 10 11 12 13 class BernoulliBandit : def __init__ (self, k ) -> None : self.K = k """初始化K个老虎机,每个老虎机的获奖概率不同""" self.probs = np.random.uniform(size=k) self.best_prob_idx = np.argmax(self.probs) self.best_prob = self.probs[self.best_prob_idx] def step (self, k ): """ 选择第K个摇杆 """ return 1 if np.random.rand() < self.probs[k] else 0
下面有三个算法,是在一次动作中做不同的决策,因此搭建一个总体框架
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 class Solver : """ 多臂老虎机算法基本框架 """ def __init__ (self, bandit ): self.bandit = bandit self.counts = np.zeros(self.bandit.K) self.regret = 0. self.actions = [] self.regrets = [] def update_regret (self, k ): """后悔值就是距离最优老虎机获奖概率的差距,这个越小说明选择的老虎机越优""" self.regret += self.bandit.best_prob - self.bandit.probs[k] self.regrets.append(self.regret) def run_one_step (self ): raise NotImplementedError def run (self, num_steps ): for _ in range (num_steps): k = self.run_one_step() self.counts[k] += 1 self.actions.append(k) self.update_regret(k) """为什么会有这个函数是因为我在jupyter中实验因为没有对每个算法进行初始化,导致不同算法间的迭代次数不一样不能绘图""" def reset (self ): self.counts = np.zeros(self.bandit.K) self.regret = 0. self.actions = [] self.regrets = []
ϵ-贪心算法 这个算法的核心就是每次决策过程中,以1-ε的概率选择已知期望最优的老虎机,以ε的概率随机选取 所以要多维护一个期望
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class EpsilonGreedy (Solver ): """ epsilon贪婪算法,继承Solver类 """ """注意这里的init_prob要等与1.0不能等于1,不然之后的后悔值全是线性的,我也不知道为什么""" def __init__ (self, bandit, epsilon=0.01 , init_prob=1.0 ): super (EpsilonGreedy, self).__init__(bandit) self.epsilon = epsilon self.init_prob = init_prob self.estimates = np.array([init_prob] * self.bandit.K) def run_one_step (self ): if np.random.random() < self.epsilon: k = np.random.randint(0 , self.bandit.K) else : k = np.argmax(self.estimates) r = self.bandit.step(k) """期望的迭代更新,公式 E = E + 1/N(reward - E)""" self.estimates[k] += 1. / (self.counts[k] + 1 ) * (r - self.estimates[k]) return k def reset (self ): super ().reset() self.estimates = np.array([self.init_prob] * self.bandit.K)
这个算法ε越小最后累计的后悔值越小,因为ε越小的话算符自主探索的可能性就更小,每一次迭代都会最大可能地选取期望最高的老虎机 但是我感觉ε太小了可能会导致算法一直找不到最优的老虎机。 因此有了下面的递减ε算法,ε赋值为时间步的倒数,这样就会在开始时候让算法自由探索,再在一定时间步后尽可能地选取期望最高的老虎机, 这个方法也是这一节中后悔值后期累计最小最不容易变的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class DecayEpsilonGreedy (Solver ): """ decay epsilon贪婪算法,继承Solver类,这里的epsilon变成时间的倒数 """ def __init__ (self, bandit, init_prob=1.0 ): super (DecayEpsilonGreedy, self).__init__(bandit) self.time_count = 0 self.init_prob = init_prob self.estimates = np.array([init_prob] * self.bandit.K) def run_one_step (self ): self.time_count += 1 if np.random.random() < 1 /self.time_count: k = np.random.randint(0 , self.bandit.K) else : k = np.argmax(self.estimates) r = self.bandit.step(k) self.estimates[k] += 1. / (self.counts[k] + 1 ) * (r - self.estimates[k]) return k def reset (self ): super ().reset() self.time_count = 0 self.estimates = np.array([self.init_prob] * self.bandit.K)
上置信界算法 这个算法后悔值叠加的是最大的,它的依据来自于霍夫丁不等式 X1…..Xn属于[0,1],那么P{E[X] >= x_n + u } < e^(-2nu^2), 其中x
_n是x的n个采样均值,u是不确定度。 我们取等式右边为概率p,Q(a)为期望,那么我们就可以知道P{Q(a) < Q`(a) + u}>1-p, 此时如果p非常小,那么就有趋近于1的概率我们能取到动作价值的上界 但我们仍然不知道不确定度是多少,我们又希望p能随着算法的进行越来越小,那么我们就让p=1/t 然后再依据p=e^(-2nu^2)计算出不确定度,这个就是我们要找的上界,每次迭代过程中选择上界最大的那个动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class UCB (Solver ): def __init__ (self, bandit, coef=1. , init_prob=1.0 ): super (UCB, self).__init__(bandit) self.estimates = np.array([init_prob] * bandit.K) self.coef = coef self.total_count = 0 self.init_prob = init_prob def run_one_step (self ): self.total_count += 1 upper_bouds = self.estimates + self.coef*np.sqrt(np.log(self.total_count)/(2 *(self.counts + 1 ))) k = np.argmax(upper_bouds) reward = self.bandit.step(k) self.estimates[k] += 1. /(self.counts[k] + 1 ) * (reward - self.estimates[k]) return k def reset (self ): super ().reset() self.total_count = 0 self.estimates = np.array([self.init_prob] * self.bandit.K)
汤普森采样算法 这里面有个beta分布我其实没有太搞懂,不过看代码就能知道个大概 这个算法的思路就是每次在参数为(每个老虎机获得奖励的次数,每个老虎机没获得奖励的次数)的beta分布中采样 选取值最大的那个为idx作为动作 后续有个画图我们可以看到随着算法的进行,能更高概率获得奖励的老虎机第一个参数越来越大,被采样的可能性也被提高,而那些很难获得奖励的则可能性被降低了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class TompsonSampling (Solver ): def __init__ (self, bandit ): super (TompsonSampling, self).__init__(bandit) self._a = np.ones(self.bandit.K) self._b = np.ones(self.bandit.K) self.probs = [] def run_one_step (self ): samples = np.random.beta(self._a, self._b) self.probs.append(samples) k = np.argmax(samples) reward = self.bandit.step(k) self._a[k] += reward self._b[k] += 1. - reward return k def reset (self ): super ().reset() self._a = np.ones(self.bandit.K) self._b = np.ones(self.bandit.K) self.probs = []
后续一定要了解一下beta分布到底是什么东西
然后是一些utils
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 def plot_result (solvers, solver_names ): for idx, solver in enumerate (solvers): time_list = range (len (solvers[0 ].regrets)) plt.plot(time_list, solver.regrets, label=solver_names[idx]) plt.xlabel("time step" ) plt.ylabel("cumulate regrets" ) plt.title(f"{solvers[0 ].bandit.K} -arms" ) plt.legend() plt.show() def run_all (solvers, solver_names, random_seed=1 , T=5000 ): np.random.seed(random_seed) for s in solvers: s.reset() s.run(T) plot_result(solvers, solver_names) solvers = [ EpsilonGreedy(bandit), DecayEpsilonGreedy(bandit), UCB(bandit) ] solver_names = [ "eps_bandit" , "dc_eps_bandit" , "ucb_bandit" ] run_all(solvers, solver_names, random_seed=42 , T=5000 )
todo