实现TD-Gammon算法

我正在尝试实现Gerald Tesauro在TD-Gammon文章中提出的算法。学习算法的核心在下面的段落中描述:

enter image description here

我决定使用单一隐藏层(如果这在1990年代初期就足以玩世界级的西洋双陆棋,那么对我来说也足够了)。我非常确定除了train()函数之外的所有部分都是正确的(它们更容易测试),但我不知道我是否正确实现了这个最终算法。

import numpy as npclass TD_network:    """    具有单一隐藏层的神经网络,并采用G. Tesauro 1995年TD-Gammon文章中的时间差分训练算法    """    def __init__(self, num_input, num_hidden, num_output, hnorm, dhnorm, onorm, donorm):        self.w21 = 2*np.random.rand(num_hidden, num_input) - 1        self.w32 = 2*np.random.rand(num_output, num_hidden) - 1        self.b2 = 2*np.random.rand(num_hidden) - 1        self.b3 = 2*np.random.rand(num_output) - 1        self.hnorm = hnorm        self.dhnorm = dhnorm        self.onorm = onorm        self.donorm = donorm    def value(self, input):        """评估神经网络输出"""        assert(input.shape == self.w21[1,:].shape)        h = self.w21.dot(input) + self.b2        hn = self.hnorm(h)        o = self.w32.dot(hn) + self.b3        return(self.onorm(o))    def gradient(self, input):        """        计算神经网络在给定输入处的梯度。输出一个字典列表,        每个字典对应一个输出节点的梯度,每个字典中的元素给出权重子集的梯度。        """         assert(input.shape == self.w21[1,:].shape)        J = []        h = self.w21.dot(input) + self.b2        hn = self.hnorm(h)        o = self.w32.dot(hn) + self.b3        for i in range(len(self.b3)):            db3 = np.zeros(self.b3.shape)            db3[i] = self.donorm(o[i])            dw32 = np.zeros(self.w32.shape)            dw32[i, :] = self.donorm(o[i])*hn            db2 = np.multiply(self.dhnorm(h), self.w32[i,:])*self.donorm(o[i])            dw21 = np.transpose(np.outer(input, db2))            J.append(dict(db3 = db3, dw32 = dw32, db2 = db2, dw21 = dw21))        return(J)    def train(self, input_states, end_result, a = 0.1, l = 0.7):        """        使用代表从开始到结束的一系列输入状态训练网络,并为最终状态提供最终(监督/期望)输出        """        outputs = [self(input_state) for input_state in input_states]        outputs.append(end_result)        for t in range(len(input_states)):            delta = dict(                db3 = np.zeros(self.b3.shape),                dw32 = np.zeros(self.w32.shape),                db2 = np.zeros(self.b2.shape),                dw21 = np.zeros(self.w21.shape))            grad = self.gradient(input_states[t])            for i in range(len(self.b3)):                for key in delta.keys():                    td_sum = sum([l**(t-k)*grad[i][key] for k in range(t + 1)])                    delta[key] += a*(outputs[t + 1][i] - outputs[t][i])*td_sum            self.w21 += delta["dw21"]            self.w32 += delta["dw32"]            self.b2 += delta["db2"]            self.b3 += delta["db3"]

我的使用方式是,我会玩完整的一局游戏(或者更确切地说,神经网络与自己对战),然后我将从开始到结束的游戏状态发送到train()函数中,并附上最终结果。然后它会使用这个游戏日志,并应用上述公式来调整权重,首先使用第一个游戏状态,然后是第一个和第二个游戏状态,依此类推,直到最后一次使用整个游戏状态列表。然后我会重复多次,希望网络能够学习。

需要明确的是,我并不是在寻求对我的代码编写的反馈。这原本只是一个快速而粗糙的实现,以确保我把所有零件都放在了正确的位置。

然而,我不知道它是否正确,因为到目前为止,我无法让它在任何合理的水平上玩井字游戏。可能有很多原因。也许我没有给它足够的隐藏节点(我使用了10到12个)。也许它需要更多的游戏来训练(我使用了200,000局)。也许它需要不同的归一化函数(我尝试了sigmoid和ReLU,有漏和无漏的不同变体)。也许学习参数没有调整好。也许井字游戏及其确定性玩法意味着它会“锁定”在游戏树的某些路径上。或者也许训练实现本身就是错误的。这就是我在这里的原因。

我是否误解了Tesauro的算法?


回答:

我不能说我完全理解你的实现,但这一行引起了我的注意:

                    td_sum = sum([l**(t-k)*grad[i][key] for k in range(t + 1)])

与你引用的公式相比:

我至少看到了两个不同之处:

  • 你的实现对t+1个元素求和,而公式中是对t个元素求和
  • 梯度应该使用与l**(t-k)中使用的k相同的索引,但在你的实现中,它使用了ikey进行索引,没有任何对k的引用

或许如果你修正这些差异,你的解决方案会表现得更符合预期。

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注