Я пытаюсь реализовать алгоритм из статьи TD-Gammon Джеральда. Тезауро. Ядро алгоритма обучения описано в следующем абзаце:
Я решил иметь один скрытый слой (если этого было достаточно, чтобы играть в нарды мирового класса в начале 1990-х, то и мне этого достаточно). Я почти уверен, что все, кроме функции train()
, корректно (их легче тестировать), но я понятия не имею, правильно ли я реализовал этот окончательный алгоритм.
import numpy as np
class TD_network:
"""
Neural network with a single hidden layer and a Temporal Displacement training algorithm
taken from G. Tesauro's 1995 TD-Gammon article.
"""
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):
"""Evaluates the NN output"""
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):
"""
Calculates the gradient of the NN at the given input. Outputs a list of dictionaries
where each dict corresponds to the gradient of an output node, and each element in
a given dict gives the gradient for a subset of the weights.
"""
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):
"""
Trains the network using a single series of input states representing a game from beginning
to end, and a final (supervised / desired) output for the end state
"""
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). Возможно, было бы лучше с разными функциями нормализации (я пробовал сигмовидную и ReLU, дырявую и не дырявую, в разных вариациях). Возможно, параметры обучения настроены неправильно. Возможно, крестики-нолики и их детерминированный геймплей означают, что они «запираются» на определенных путях в дереве игры. Или, может быть, реализация обучения просто неверна. Вот почему я здесь.
Я неправильно понял алгоритм Тезауро?