На стене выключатель. Нажатие которого иногда приводит к цели, иногда нет. Что означает, что выключателем может быть не то, что мы предполагаем.
Вопрос можно поставить абстрактно. Пусть имеется множество {a, b, c, d}. Некоторые из элементов могут быть состояниями, некоторые действиями.
Предположим, что действиями будут {a, b}, состояниями {c, d}. Пусть имеем: с|d=a(c), с|d=b(c), c=a(d), с|d=b(d).
Здесь «|» означает «либо». Так с|d=b(d) означает: из состояния d при действии b следует либо c, либо d.
Попробуем иначе интерпретировать. Предполагаем: действия {a, c}, состояния {b, d}. Пусть имеем: b=a(b), b|d=c(b), d=a(d), b=c(d).
Разница, если ее оценить количественно, в более однозначном поведении второй гипотезы. В первом случае коэффициент однозначности, взятый как отношение как если бы все переходы были бы однозначны к всем случившимся переходам, будет равен 4/7. Во втором случае он будет равен 4/5. Или, другими словами, мы имеем почти детерминированное пространство состояний. Для которого уже можно делать предсказания с приемлемой точностью.
Это было вступление. Теперь собственно к статье. Есть объект исследования (пространство состояний), однозначность которого достаточно высока. И есть несколько агентов, целью которых является достичь целевые состояния. Которые, в частности, могут и совпадать. Оговорюсь, что эти агенты не ведают о других агентах. Поэтому их ходы обусловлены только своими QL-картами, которые агенты формируют в результате исследования пространства состояния.
В противоположность играм, допускающих частичное или полное знание QL-карт других участников. Следствие — возможность оптимизировать маршрут при чередовании ходов, включая вариант эмпатии. Возможно, эта тема будет рассмотрена в дальнейшем.
Теперь к технической стороне описания. Загружаем сгенерированные графы load_file. В качестве демонстрации в игре участвуют три агента. Цель каждого — достичь состояния=9. Состояние=10 следует избегать.
Второй агент с загруженным graph_b — это возможность передвигаться только по черным стрелкам. Из состояния=4 имеем неоднозначность, т.к. при действии 11 может следовать либо состояние=5, либо состояние=1. Из состояния=3 ее нет, т.к действия 11 отлично от действия 13. Вызов g2.get_ku() вернет коэффициент однозначности 0.917=11/12.
Третий агент с graph_w — возможность передвигаться как по черным, так и по синим стрелкам. Для сравнения: g3.get_ku() вернет 0.933. Первый же агент с graph_w1 имеет возможности как у graph_w за исключением перехода 9=12(2), т.е перехода в 9 из 2 при действии 12 для него нет. Его коэффициент однозначности равен 0.929
{ "1": [["2", "11"], ["10", "15"]], "2": [["3", "11"]], "3": [["4", "11"], ["2", "13"], ["6", "12"]], "4": [["5|1", "11"]], "5": [["6", "11"]], "6": [["7", "11"]], "7": [["8", "11"], ["5", "12"]], "8": [["9", "11"]], "9": [], "10":[["5", "15"]] } { "1": [["2", "11"], ["10", "15"]], "2": [["3", "11"]], "3": [["4", "11"], ["2", "13"]], "4": [["5|1", "11"]], "5": [["6", "11"]], "6": [["7", "11"]], "7": [["8", "11"]], "8": [["9", "11"]], "9": [], "10":[["5", "15"]] } { "1": [["2", "11"], ["10", "15"]], "2": [["3", "11"], ["9", "12"]], "3": [["4", "11"], ["2", "13"], ["6", "12"]], "4": [["5|1", "11"]], "5": [["6", "11"]], "6": [["7", "11"]], "7": [["8", "11"], ["5", "12"]], "8": [["9", "11"]], "9": [], "10":[["5", "15"]] }
Сформируем список агентов [[g1, {e: 5, b: -6}, s, e, 1], [g2, {e: 5, b: -6}, s, e, 1], [g3, {e: 5, b: -6}, s, e, 1]]. Зададим количество игр k=1.
Здесь первый игрок who=0 сформирует QL-карту, по которой будет проложен маршрут [1,2,3,6,7,8,9]. Второй агент проходит последовательно все состояния. Третьему агенту доступен самый короткий маршрут.
При чередовании ходов маршрут до цели выглядит иначе. Маршрут представлен кортежами (агент, состояние). Из начального состояния=1 агент who=0 переходит в состояние=2. Далее who=1 переходит в состояние=3. Затем who=2 согласно своей QL-карте возвращается назад в состояние=2 (именно так выглядит его оптимальный путь). Первый агент из состояния=2 выбирает состояние=3, т.к это единственный путь к цели в его представлении.
Видно, что из состояния=4 третий агент попадает в состояние=1 (в силу неоднозначности выбора из состояния=4). Маршрут зацикливается. И только со второй попытки третий агент попадает в состояние=5. Далее последовательно до состояния=9 с чередованием ходов. В выигрыше оказывается агент who=0.
Теперь сгенерируем 100 игр. Результат ниже.
Для второго агента оказалось, что маршрута до цели не существует по результату последней игры. Так вышло, потому что из состояния=3 до состояния=4 ql=1.57, а до состояния=2 ql=2.39. Иначе говоря, состояния 2 и 3 зацикливаются. Почему так QL-карта сформировалась? Следствие (косвенное) неоднозначности при переходе из состояния=4.
Увеличим количество ходов=4 за раз для второго агента.
Видно, что в последней игре он последовательно выбирает [3, 4, 1, 2]. В выигрыше из 100 игр три таком порядке ходов оказывается в большинстве случаем третий агент.
Код ниже. Каких-то сложностей не несет.
import random, pickle, json class GenGraph(): def __init__(self, f_list, x_list, n=4): '''f_list - список действий, x_list - список состояний' Целесобразность в списке действий возникает, если соеденять графы''' self.n = n self.graph = self.initG(f_list, x_list) def rnd_get(self, v): return random.choice(v.split("|")) def show(self): for x in self.graph: print(x, ":", self.graph[x]) def return_graph(self): return self.graph def get_ku(self): '''return (коэффициент однозначности, размер)''' n = 0; u = 0 for x in self.graph: for y in self.graph[x]: u += y[0].count("|") n += 1 if n != 0: return (round(1- u/(n+u), 3), n) else: return (None, None) def initG(self, f_list, x_list): rnd_dict = dict() r = len(x_list) rnd_list = x_list[:] for x in rnd_list: min21 = random.randint(0, len(rnd_list)//self.n) min22 = random.randint(0, len(rnd_list)//self.n) if min21 <= min22: k=random.randint(min21, min22) rnd_x = list(set(random.choices(rnd_list, k=k))) rnd_fx = [(x, random.choice(f_list)) for x in rnd_x] else: k=random.randint(min22, min21) rnd_x = list(set(random.choices(rnd_list, k=k))) rnd_fx = [(x, random.choice(f_list)) for x in rnd_x] rnd_dict[x] = rnd_fx return rnd_dict def add_uncertainty(self, u=0): '''Добавлем неоднозначность''' for i in range(u): w = random.choice(list(self.graph.keys())) if self.graph[w] != []: x = random.choice(self.graph[w]) add = random.choice(list(self.graph.keys())) if add not in x[0].split('|'): x0 = x[0] + '|' + add ind = self.graph[w].index(x) self.graph[w][ind] = [x0, x[1]] class Ql(GenGraph): def __init__(self, f_list, x_list): super().__init__(f_list, x_list) self.visited = {} self.ql = {} self.gamma = 0.9 def training(self, target, current=None, depth=1, epochs=1): '''Получаем QL-карту: помечаем состояния с дисконтом от цели''' ls = sorted(list(target.items()), key = lambda t: t[1], reverse = True) if ls[0][0] not in self.graph or current not in self.graph: return for epoch in range(1, epochs+1): if current == None: current = random.choice(list(self.graph)) if current not in self.visited: self.visited[current] = 1 self.ql[current] = 0 n = depth while n > 0: if current in target: self.ql[current] = target[current] if self.graph[current] == []: vs = sorted(self.visited.items(), key = lambda t: t[1]) vs = [x for x in vs if vs[0][1] == x[1]] vq = [x for x in vs if self.ql[x[0]] == 0] if vq: current = vq[0][0] else: current = random.choice(vs)[0] self.visited[current] +=1 neighbors = [random.choice(x[0].split('|')) for x in self.graph[current]] if not neighbors: n -=1 continue for x in neighbors: if x not in self.visited: self.visited[x] = 0 self.ql[x] = 0 ql_sort = sorted([(x, self.ql[x]) for x in neighbors], key = lambda t: t[1], reverse=True) ql_max = random.choice([x for x in ql_sort if ql_sort[0][1] == x[1]]) if current not in target: self.ql[current] = round(self.gamma*ql_max[1], 2) visited_sort = sorted([(x, self.visited[x]) for x in neighbors], key = lambda t: t[1]) visited_min = random.choice([x for x in visited_sort if visited_sort[0][1] == x[1]]) self.visited[visited_min[0]] +=1 current = visited_min[0] n -=1 return current def get_ql_path(self, current, target, depth=100): '''маршрут до цели''' path = [current] while depth > 0: if current == None or target not in self.graph: return [] if current == target: return path mx = 0 neighbor_mx = None neighbors = [random.choice(x[0].split('|')) for x in self.graph[current]] for neighbor in neighbors: #if neighbor in self.ql and neighbor not in path: if neighbor in self.ql: if self.ql[neighbor] >= mx: mx = self.ql[neighbor] neighbor_mx = neighbor else: continue current = neighbor_mx path += [current] depth -=1 return [] def save_file(self, name, t_data, t='pickle'): f = open(name, 'wb') if t == 'pickle': if t_data == 'graph': pickle.dump(self.graph, f) if t_data == 'visited': pickle.dump(self.visited, f) if t_data == 'ql': pickle.dump(self.ql, f) if t == 'json': if t_data == 'graph': json.dump(self.graph, open(name, 'w')) if t_data == 'visited': json.dump(self.visited, open(name, 'w')) if t_data == 'ql': json.dump(self.ql, open(name, 'w')) f.close() def load_file(self, name, t_data, t='pickle'): f = open(name, 'rb') if t == 'pickle': if t_data == 'graph': self.graph = pickle.load(f) if t_data == 'visited': self.visited = pickle.load(f) if t_data == 'ql': self.ql = pickle.load(f) if t == 'json': if t_data == 'graph': self.graph = json.load(open(name)) if t_data == 'visited': self.visited = json.load(open(name)) if t_data == 'ql': self.ql = json.load(open(name)) f.close() class Game(): def __init__(self, agents, depth=100): self.agents = agents #список агентов self.depth = depth #глубина поиска def run(self): cur_agent = [None]*len(self.agents) for i in range(len(self.agents)): cur_agent[i] = self.agents[i][2] for i in range(self.depth): for j in range(len(self.agents)): who = i%len(self.agents) next_who = (i+1)%len(self.agents) cur = agents[who][0].training(agents[who][1], current=cur_agent[who], depth=1, epochs=1) cur_agent[who] = cur for i in range(len(agents)): who = i%len(self.agents) next_who = (i+1)%len(self.agents) print() print("who = ", who, " ql = \n", agents[who][0].ql) print("who = ", who, " path = ", agents[who][0].get_ql_path(agents[who][2], agents[who][3], depth=100)) agents[who][0].show() print() win = [(None, agents[0][2])] for i in range(self.depth): who = i%len(self.agents) next_who = (i+1)%len(self.agents) for step in range(1, agents[who][4]+1): path = agents[who][0].get_ql_path(agents[who][2], agents[who][3], depth=100) if path: cur = path[1] if cur == agents[who][3]: win.append((who, cur)) return (who, win) if step == agents[who][4]: agents[next_who][2] = cur win.append((who, cur)) break else: agents[who][2] = cur win.append((who, cur)) else: win.append((who, "pass")) self.agents[next_who][2] = self.agents[who][2] break return (-1, win) #никто не выигал if __name__ == "__main__": agents_won = [] #кто сколько выиграл k = 1 #количество игр #Списки по состояниям и действиям выбраны произвольно. Применительно #к этой игре роли не играют. Действия имеют значение, если соединять графы for i in range(1, k+1): g1 = Ql([str(f) for f in range(10, 20)], [str(x) for x in range(70, 90)]) g2 = Ql([str(f) for f in range(10, 20)], [str(x) for x in range(70, 90)]) g3 = Ql([str(f) for f in range(10, 20)], [str(x) for x in range(70, 90)]) g1.load_file('graph_w1.json', t_data = 'graph', t='json') g2.load_file('graph_b.json', t_data = 'graph', t='json') g3.load_file('graph_w.json', t_data = 'graph', t='json') # s - начальное состояние, e - конечное состояние, b - нежелательное состояние s = '1'; e = '9'; b = '10' # последний элемент в списке каждого агента - количество ходов за раз agents = [[g1, {e: 5, b: -6}, s, e, 1], [g2, {e: 5, b: -6}, s, e, 1], [g3, {e: 5, b: -6}, s, e, 1]] if len(agents_won) != len(agents): agents_won = [0]*len(agents) gm = Game(agents, depth=100) r = gm.run() if r[0] > -1: print("agent ", r[0], " won: ", r[1]) if r[0] == 0: agents_won[0] +=1 if r[0] == 1: agents_won[1] +=1 if r[0] == 2: agents_won[2] +=1 else: print("agents won: ", []) print("\n The agents won: ", agents_won)
ссылка на оригинал статьи https://habr.com/ru/post/671076/
Добавить комментарий