实现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

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

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